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