Spring 的AOP底层实现原理
首先,我们实现InvocationHandler接口,该接口定义了一个invoke(Objectproxy,Methodmethod,Object[]args)的方法,proxy是代理实例,一般不会用到;method是代理实例上的方法,通过它可以发起对目标类的反射调用;args是通过代理类传入的方法参数,在反射调用时使用。
此外,我们在构造函数里通过target传入真实的目标对象,如①处所示,在接口方法invoke(Objectproxy,Methodmethod,Object[]args)里,将目标类实例传给method.invoke()方法,通过反射调用目标类方法,如②所示。
下面,我们通过Proxy结合PerformaceHandler创建ForumService接口的代理实例,如代码清单7所示:
代码清单7TestForumService:创建代理实例
packagecom.baobaotao.proxy;importjava.lang.reflect.Proxy;publicclassTestForumService{publicstaticvoidmain(String[]args){ForumServicetarget=newForumServiceImpl();//①目标业务类//②将目标业务类和横切代码编织到一起PerformaceHandlerhandler=newPerformaceHandler(target);//③为编织了目标业务类逻辑和性能监视横切逻辑的handler创建代理类ForumServiceproxy=(ForumService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);//④操作代理实例proxy.removeForum(10);proxy.removeTopic(1012);}}
上面的代码完成了业务类代码和横切代码编织和接口代理实例生成的工作,其中在②处,我们将ForumService实例编织为一个包含性能监视逻辑的PerformaceHandler实例,然后在③处,通过Proxy的静态方法newProxyInstance()为融合了业务类逻辑和性能监视逻辑的handler创建一个ForumService接口的代理实例,该方法的第一个入参为类加载器,第二个入参为创建的代理实例所要实现的一组接口,第三个参数是整合了业务逻辑和横切逻辑的编织器对象。
按照③处的设置方式,这个代理实例就实现了目标业务类的所有接口,也即ForumServiceImpl的ForumService接口。这样,我们就可以按照调用ForumService接口的实例相同的方式调用代理实例,如④所示。运行以上的代码,输出以下的信息:
beginmonitor...模拟删除Forum记录:10endmonitor...com.baobaotao.proxy.ForumServiceImpl.removeForum花费47毫秒。beginmonitor...模拟删除Topic记录:1012endmonitor...com.baobaotao.proxy.ForumServiceImpl.removeTopic花费26毫秒。
我们发现,程序的运行效果和直接在业务类中编写性能监视逻辑的效果一致,但是在这里,原来分散的横切逻辑代码已经被我们抽取到PerformaceHandler中。当其它业务类(如UserService、SystemService等)的业务方法也需要使用性能监视时,我们只要按照以上的方式,分别为它们创建代理对象就可以了。下面,我们用时序图描述调用关系,进一步代理实例的本质,如图1所示:
图1:代理实例的时序图
我们在上图中特别使用虚线阴影的方式对通过代理器创建的ForumService实例进行凸显,该实例内部利用PerformaceHandler整合横切逻辑和业务逻辑。调用者调用代理对象的的removeForum()和removeTopic()方法时,上图的内部调用时序清晰地告诉了我们实际上所发生的一切。
CGLib动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理,这一点我们从Proxy的接口方法newProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandlerh)就看得很清楚,第三个入参interfaces就是为代理实例指定的实现接口。虽然,面向接口的编程被很多很有影响力人(包括RodJohnson)的推崇,但在实际开发中,开发者也遇到了很多困惑:难道对一个简单业务表的操作真的需要创建5个类(领域对象类、Dao接口,Dao实现类,Service接口和Service实现类)吗?对于这一问题,我们还是留待大家进一步讨论。现在的问题是:对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。你可以从http://cglib.sourceforge.net/获取CGLib的类包,也可以直接从Spring的关联类库lib/cglib中获取类包。
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并在拦截方法相应地织入横切逻辑。下面,我们采用CGLib技术,编写一个可以为任何类创建织入性能监视横切逻辑的代理对象的代理器,如代码清单8所示:
代码清单8CglibProxy
packagecom.baobaotao.proxy;importjava.lang.reflect.Method;importnet.sf.cglib.proxy.Enhancer;importnet.sf.cglib.proxy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;publicclassCglibProxyimplementsMethodInterceptor{privateEnhancerenhancer=newEnhancer();publicObjectgetProxy(Classclazz){enhancer.setSuperclass(clazz);①设置需要创建子类的类enhancer.setCallback(this);returnenhancer.create();②通过字节码技术动态创建子类实例}publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());Objectresult=proxy.invokeSuper(obj,args);③通过代理类调用父类中的方法PerformanceMonitor.end();returnresult;}}
在上面代码中,你可以通过getProxy(Classclazz)为一个类创建动态代理对象,该代理对象是指定类clazz的子类。在这个代理对象中,我们织入性能监视的横切逻辑(粗体部分)。intercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)是CGLib定义的Inerceptor接口的方法,obj表示父类的实例,method为父类方法的反射对象,args为方法的动态入参,而proxy为代理类实例。
下面,我们通过CglibProxy为ForumServiceImpl类创建代理对象,并测试代理对象的方法,如代码清单9所示:
代码清单9TestForumService:测试Cglib创建的代理类
packagecom.baobaotao.proxy;importjava.lang.reflect.Proxy;publicclassTestForumService{publicstaticvoidmain(String[]args){CglibProxyproxy=newCglibProxy();ForumServiceImplforumService=//①通过动态生成子类的方式创建代理对象(ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);forumService.removeForum(10);forumService.removeTopic(1023);}}
在①中,我们通过CglibProxy为ForumServiceImpl动态创建了一个织入性能监视逻辑的代理对象,并调用了代理对象的业务方法。运行上面的代码,输入以下的信息:
beginmonitor...模拟删除Forum记录:10endmonitor...com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0.removeForum花费47毫秒。beginmonitor...模拟删除Topic记录:1023endmonitor...com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0.removeTopic花费16毫秒。
观察以上的输出,除了发现两个业务方法中都织入了性能监控的逻辑外,我们还发现代理类的名字是com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0,这个特殊的类就是CGLib为ForumServiceImpl所动态创建的子类。
小结
SpringAOP在底层就是利用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。在这里,我们对以上两节动态创建代理对象做一个小结。
在PerformaceHandler和CglibProxy中,有三点值得注意的地方是:第一,目标类的所有方法都被添加了性能监视横切的代码,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些方法织入横切代码;第二,我们手工指定了织入横切代码的织入点,即在目标类业务方法的开始和结束前调用;第三,我们手工编写横切代码。以上三个问题,在AOP中占用重要的地位,因为SpringAOP的主要工作就是围绕以上三点展开:SpringAOP通过Pointcut(切点)指定在哪些类的哪些方法上施加横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等),此外,Spring还通过Advisor(切面)组合Pointcut和Advice。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术为目标Bean创建织入切面的代理对象了。
JDK动态代理所创建的代理对象,在JDK1.3下,性能强差人意。虽然在高版本的JDK中,动态代理对象的性能得到了很大的提高,但是有研究表明,CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。而CGLib在创建代理对象时性能却比JDK动态代理慢很多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为不需要频繁创建代理对象,所以比较适合用CGLib动态代理技术,反之适合用JDK动态代理技术。此外,由于CGLib采用生成子类的技术创建代理对象,所以不能对目标类中的final方法进行代理。
转载:http://www.51cto.com/art/200702/39480_2.htm