81540991 2010-12-19
JDK动态代理
在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在我们终于发现动态代理是实现AOP的绝好底层技术。
JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
而Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例。这样讲一定很抽象,我们马上着手动用Proxy和InvocationHandler这两个魔法戒对上一节中的性能监视代码进行AOP式的改造。
首先,我们从业务类ForumServiceImpl中删除性能监视的横切代码,使ForumServiceImpl只负责具体的业务逻辑,如所示:
代码清单5ForumServiceImpl:移除性能监视横切代码
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->package com.baobaotao.proxy; public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { ① System.out.println("模拟删除Topic记录:"+topicId); try { Thread.currentThread().sleep(20); } catch (Exception e) { throw new RuntimeException(e); } ② } public void removeForum(int forumId) { ① System.out.println("模拟删除Forum记录:"+forumId); try { Thread.currentThread().sleep(40); } catch (Exception e) { throw new RuntimeException(e); } ② } }
在代码清单 5中的①和②处,原来的性能监视代码被移除了,我们只保留了真正的业务逻辑。
从业务类中移除的横切代码当然还得找到一个寄居之所,InvocationHandler就是横切代码的家园乐土,我们将性能监视的代码安置在PerformaceHandler中,如代码清单6所示:
代码清单6PerformaceHandler
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->package com.baobaotao.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class PerformaceHandler implements InvocationHandler { private Object target; public PerformaceHandler(Object target){//①target为目标的业务类 this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName()); Object bj = method.invoke(target, args);//②通过反射方法调用目标业务类的业务方法 PerformanceMonitor.end(); return obj; } }
粗体部分的代码为性能监视的横切代码,我们发现,横切代码只出现一次,而不是原来那样星洒各处。大家注意②处的method.invoke(),该语句通过反射的机制调用目标对象的方法,这样InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法就将横切代码和目标业务类代码编织到一起了,所以我们可以将InvocationHandler看成是业务逻辑和横切逻辑的编织器。下面,我们对这段代码做进一步的说明。
首先,我们实现InvocationHandler接口,该接口定义了一个invoke(Objectproxy,Methodmethod,Object[]args)的方法,proxy是代理实例,一般不会用到;method是代理实例上的方法,通过它可以发起对目标类的反射调用;args是通过代理类传入的方法参数,在反射调用时使用。
此外,我们在构造函数里通过target传入真实的目标对象,如①处所示,在接口方法invoke(Objectproxy,Methodmethod,Object[]args)里,将目标类实例传给method.invoke()方法,通过反射调用目标类方法,如②所示。
下面,我们通过Proxy结合PerformaceHandler创建ForumService接口的代理实例,如代码清单7所示:
代码清单 7 TestForumService:创建代理实例<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->package com.baobaotao.proxy; import java.lang.reflect.Proxy; public class TestForumService { public static void main(String[] args) { ForumService target = new ForumServiceImpl();//①目标业务类 //② 将目标业务类和横切代码编织到一起 PerformaceHandler handler = new PerformaceHandler(target); //③为编织了目标业务类逻辑和性能监视横切逻辑的handler创建代理类 ForumService proxy = (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接口的实例相同的方式调用代理实例,如④所示。运行以上的代码,输出以下的信息:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->begin monitor... 模拟删除Forum记录:10 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeForum花费47毫秒。 begin monitor... 模拟删除Topic记录:1012 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeTopic花费26毫秒。
我们发现,程序的运行效果和直接在业务类中编写性能监视逻辑的效果一致,但是在这里,原来分散的横切逻辑代码已经被我们抽取到PerformaceHandler中。当其它业务类(如UserService、SystemService等)的业务方法也需要使用性能监视时,我们只要按照以上的方式,分别为它们创建代理对象就可以了。下面,我们用时序图描述调用关系,进一步代理实例的本质,如图1所示:
图1代理实例的时序图
我们在上图中特别使用虚线阴影的方式对通过代理器创建的ForumService实例进行凸显,该实例内部利用PerformaceHandler整合横切逻辑和业务逻辑。调用者调用代理对象的的removeForum()和removeTopic()方法时,上图的内部调用时序清晰地告诉了我们实际上所发生的一切。
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。④ Mapper.xml文件中的namespace即是mapper接口的类路径。