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

来源:互联网 发布:音乐广告合成软件 编辑:程序博客网 时间:2024/05/05 22:27

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

0 0