Spring 源码梳理(九) AOP与动态代理

Spring 源码梳理(九) AOP与动态代理

SpringAOP的核心是动态代理,就从动态代理开始分析,然后就会谈到静态代理,以及为什么会有代理这个说法,后面逐一分析:

1.什么是代理,为什么会有代理?

类甲的代理就是代替甲来完成甲的功能,比如实际上就是有点像房产中介,本身不提供房产,但是可以帮助开发商把房子卖出去,并且在卖的过程中还能做一些操作,赚的利益。那为什么会有代理呢? 就类甲来说,我们想执行这个类甲的相关方法,但是在执行的前后想做一些其他的操作,由此就想到了一个方法,就是重写一个类乙,把甲的方法都重写一遍,还加上了自己的东西,这就是静态代理。

2.静态代理,如下图:


上面是典型的静态的代理模式,我们看一下示例代码:

Subject

package day_20161010;

public interface Subject {
    public int add(int a,int b);  
    public int div(int a,int b);
}
RealSubject

package day_20161010;

public class RealSubject implements Subject{

	public int add(int a, int b) {
		return a+b;
	}

	public int div(int a, int b) {
		return  a/b;
	}

}
Proxy

package day_20161010;

public class Proxy implements Subject{
	private Subject subject;
	
	public Proxy(Subject subject){
		this.subject = subject;
	}
	
	public int add(int a, int b) {
		System.out.println("add before...");
		return subject.add(a, b);
	}

	public int div(int a, int b) {
		System.out.println("div before...");
		return subject.div(a, b);
	}
}
Main

package day_20161010;

public class Main {
	public static void main(String[] args) {
		RealSubject realSubject = new RealSubject();
		Subject subject = new Proxy(realSubject);
		subject.add(1, 2);
		subject.div(3, 1);
	}
}
代码很清晰,只是这样的一个代理是一个java类,1)需要重写每一个接口的方法 2)如果我们增加或减少实现的方法,在代理类中也要增加或减少 3)当代理类想改变对每个实现类方法前后的操作时,则要对所有的代理方法进行更改,如何避免这三种情况,下面进行分析。

3.先看一张图,图的流程已经在这一篇文章中讲述过http://blog.csdn.net/Jintao_Ma/article/details/52710960,本篇只看其中的红色部分:


我们看到运行期系统有这样一种能力,它能够在运行期间,根据.class的组织结构动态的生成.class文件的字节码,然后再由类加载器进行加载。由于这个能力,我们可以不再使用静态代理,而让代理class类在运行期期间动态生成。【PS:不知道是为了实现动态代理而让运行期系统具备这种能力,还是运行期先有这种能力从而正好实现动态代理,这点就先不用管了】

如何利用这种能力,对类进行动态代理呢?

1)使用ASM和Javassist框架来手动在运行期系统中动态的生成代理class文件。但是使用起来手动创建了很多的业务代码,同样增加了复杂度

2)JDK本身给我们提供了一种较好的实现,如下:

InvocationHandler角色的由来
仔细思考代理模式中的代理Proxy角色。Proxy角色在执行代理业务的时候,无非是在调用真正业务之前或者之后做一些“额外”业务,如图:


由上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invocation Handler。

动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色,它的作用如下:

在静态代理中,代理Proxy中的方法,都指定了调用了特定的realSubject中的对应的方法,在上面的静态代理模式下,Proxy所做的事情,无非是调用在不同的request时,调用触发realSubject对应的方法,动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:

package day_20160928;

public class mathCount implements Count{  
    public int add(int a,int b){  
        return  a+b;  
    }  
    public int div(int a,int b){  
        return  a/b;  
    }  
}

4.前面一片文章分析过动态代理,但是不够深刻http://blog.csdn.net/Jintao_Ma/article/details/51107096,下面做一个深刻的实例,代码中的注释有详细的解释: 

Count

package day_20160928;

public interface Count {
    public int add(int a,int b);  
    public int div(int a,int b);
    public static int a = 1;
}
mathCount
package day_20160928;

public class mathCount implements Count{  
    public int add(int a,int b){  
        return  a+b;  
    }  
    public int div(int a,int b){  
        return  a/b;  
    }  
}
dyProxy

package day_20160928;

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
  
public class dyProxy implements InvocationHandler{  
    private Object subject;  
    public dyProxy(Object subject){  
        this.subject = subject;  
    }  
    /**
     * 当代理对象调用真实对象的方法时,会自动跳转到其关联的handler对象的invoke方法,
     * 就是下面的这个方法
     * @param proxy 就是我们调用哪一个代理类,就会把哪一个代理类传进来,就是Main中的count
     * @param method args  在使用代理类的方法时会传进来方法名和参数
     */  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	int b = ((Count)proxy).a;
    	System.out.println("dyProxy.invoke():"+b);
        /*在执行代理对象的方法前,添加自己的操作*/  
        System.out.println("before method:");  
        System.out.println("the method:"+method); 
        try {  
            /*以反射的方式调用真实对象中的方法*/  
            method.invoke(subject, args);
        } catch (Exception e) {  
            e.printStackTrace();  
            /*出现异常打印信息*/  
            System.out.println("exception:");   
        }  
        /*在执行代理对象的方法后,添加自己的操作*/  
        System.out.println("after method:");  
          
        return 1;  
    }  
} 
Main

package day_20160928;

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
  
public class Main {  
    public static void main(String[] args) {  
        /*建立要代理的真实对象*/  
        mathCount realcount = new mathCount();  
        /*我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的*/  
        InvocationHandler invocationHandler = new dyProxy(realcount);  
        
        /**通过Proxy的newProxyInstance来创建我们的代理对象 
         * @param handler.getClass().getClassLoader(), 这里就是在运行期期间动态地生成二进制字节码,
         * 然后重新用类加载器进行加载(就是本篇文章第二张图中讲的动态生成类然后用类加载器加载)。
         * @param realSubject.getClass().getInterfaces(),标识把这些接口与触发器invocationHandler关联起来,
         * 以后代理类执行到这些接口时,就会调用invocationHandler的invoke方法;同时,realcount.getClass().
         * getInterfaces()表示要代理的类必须有实现的接口
         * @param handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         * @return  count
         * 表示生成的代理类也只能代理接口中的方法,如Count count = (Count)
         */  
        Count count = (Count)Proxy.newProxyInstance(realcount.getClass().getClassLoader(),  
                realcount.getClass().getInterfaces(), invocationHandler);  
          
        count.add(1, 2);  
        count.div(3, 1);  
    }  
}
我们分析下动态代理是如何解决静态代理的那三个问题的:

1)首先不需要重写接口的每一个方法,使用method.invoke(subject, args)一句就可以了

2)我们在接口中和实现类中增加方法,在代理类中还是method.invoke(subject, args)就够了

3)在方法的前后进行更改时,也只需要在method.invoke(subject, args)的前后进行更改可以了。

有人说,静态代理当有很多的实现类的时候会产生多个代理类,很明显不对,我们看上面静态代理例子中的Proxy,构造方法可以接受任何实现类,而只需要Proxy就可以了。所以这并不是动态代理相对于静态代理的优点,真正的优点是上面的三点。用一张图来描述:


咦?怎么看起来那么像AOP啊。没错,这就是AOP所宣扬的那样面向切面, AOP本来就是依据动态代理来的。

5.所谓代理,就是要保证代理类和被代理类具有相同的功能;那怎么保证?

 一种是继承同一个的接口,就像上面JDK实现中的动态代理,它使用的是接口的形式; 我们也发现了一个问题,上面JDK动态代理这样一段代码:

Count count = (Count)Proxy.newProxyInstance(realcount.getClass().getClassLoader(),  
                realcount.getClass().getInterfaces(), invocationHandler);
我们看到realcount.getClass(),这段代码实际上我们是把实现类中的接口信息与触发器invocationHandler绑定,也就是说如果实现类有接口中没有的方法是不能被代理的。总的来说,JDK这种代理1)要保证有接口,2)不能代理接口中没有的方法
为避免上面的两种缺陷,这就是要说的另一种动态代理实现方式,那就是代理类继承被代理类,不需要接口,这种实现如 Cglib。 实例如下:

realSubject

package day_20161011;

public class realSubject {

	public void add()  
    {  
        System.out.println("realSubject.add()");
    }  
	
}
doProxy

package day_20161011;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;  
/* 
 * 实现了方法拦截器接口 
 */  
public class doProxy implements MethodInterceptor {  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        System.out.println("before method");  
        proxy.invokeSuper(obj, args);  
        System.out.println("after method");  
        return null;  
    }  
}  
Main

package day_20161011;

import org.springframework.cglib.proxy.Enhancer;

public class Main {
	public static void main(String[] args) {
		realSubject realsubject = new realSubject();  
	    doProxy doproxy = new doProxy();  
	    
	    //cglib 中加强器,用来创建动态代理  
	    Enhancer enhancer = new Enhancer();    
	    //设置要创建动态代理的类  
	    enhancer.setSuperclass(realsubject.getClass());    
	    //设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截  
	    enhancer.setCallback(doproxy);  
	    realSubject proxy =(realSubject)enhancer.create();  
	    proxy.add();  
	}
}
它的流程是这样的:

1).查找A上的所有非final 的public类型的方法定义;
2).将这些方法的定义转换成字节码;
3).将组成的字节码转换成相应的代理的class对象;
4).实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

6.本篇总结:其实所有AOP带来的好处都是基于一点------"在被代理的方法前后执行一些操作"(^_^)

本文参考文章:http://blog.csdn.net/luanlouis/article/details/24589193

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值