本文共 4445 字,大约阅读时间需要 14 分钟。
我们从上一篇一文中已经知道,在代理模式中代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。这就是传统的代理模式静态代理的特点。
那么传统的静态代理模式有什么问题呢?如果需要代理的类只有一个,那么静态代理没什么问题,如果有很多类需要代理呢,用静态代理的话就需要为每一个类创建一个代理类,显然这么做太过繁琐也容易出错。为此,JDK 5引入的动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。也就是说,我们不用为每个类再单独创建一个代理对象了。
比如Spring的aop框架,它可以通过简单的配置,在不新增、修改任何业务逻辑代码情况下,动态的给我们的业务逻辑增加诸如日志打印、事务处理、异常处理等,这就是利用的动态代理机制。
动态代理主要是利用了Java的反射机制。
要创建一个动态代理,只需要利用Java API提供的两个类:
java.lang.reflect.InvocationHandler
: 这是调用处理器接口,它自定义了一个 invoke()
方法,我们就在这个方法里触发代理对象自己的方法,你可以在它的前后增加我们自己的增强方法。java.lang.reflect.Proxy
: 这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象,也就是动态生成代理对象的方法。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler
接口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler
的invoke()
方法。在 invoke()
方法的参数中可以获取到代理对象、方法对应的Method
对象和调用的实际参数。invoke()
方法的返回值被返回给使用者。这种做法实际上相 当于对方法调用进行了拦截。熟悉AOP的人对这种使用模式应该不陌生。但是这种方式不需要依赖AspectJ等AOP框架。
我们可以通过Proxy.newProxyInstance()
方法来动态的创建一个代理。这个方法有3个参数:
1. ClassLoader :负责加载动态代理类2. 接口数组3. InvocationHandler:把方法调用转到代理上
用Proxy
类动态创建代理类:
InvocationHandler handler = new MyInvocationHandler();MyInterface proxy = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), new Class[] { MyInterface.class }, handler);
在执行完这段代码之后,变量proxy 包含一个 MyInterface 接口的的动态实现。所有对 proxy 的调用都被转向到实现了 InvocationHandler 接口的 handler 上。有关 InvocationHandler 的内容会在下一段介绍。
在前面提到了当你调用Proxy.newProxyInstance()
方法时,你必须要传入一个InvocationHandler
接口的实现。所有对动态代理对象的方法调用都会被转向到InvocationHandler
接口的实现上,下面是 InvocationHandler 接口的定义:
public interface InvocationHandler{ Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}
传入invoke()
方法中的proxy
参数是实现要代理接口的动态代理对象。通常你是不需要他的。invoke()
方法中的Method
对象参数代表了被动态代理的接口中要调用的方法,从这个method
对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。Object
数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。
比如我们有两个业务,要为这两个业务添加日志打印功能。如果是静态代理,那么就需要分别为每个业务类写一个代理类,而如果用动态代理,只需要实现一个日志打印功能的handler即可,完全不需要自己再单独写代理类,下面我们具体看一下这个例子。
接口A和接口B:
public interface SubjectA { public void setUser(String name,String password);}public interface SubjectB { public void sayHello(String name);}
接口A和接口B的实现:
public class RealSubjectA implements SubjectA { public void setUser(String name,String password){ System.out.println("-------------set user,name:"+name+" password:"+password+"-------------"); }}public class RealSubjectB implements SubjectB{ public void sayHello(String name) { System.out.println("--------------say hello:"+name+"-------------"); }}
/** * 日志打印handler,打印调用代理对象的方法及其参数值 * **/public class LogHandler implements InvocationHandler{ private Object proxied; LogHandler(Object proxied){ this.proxied=proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("begin to invoke method:"+method.getName()+" params:"+ Arrays.toString(args)); Object result=method.invoke(proxied,args); System.out.println("invoke "+method.getName()+" end"); return result; }}
public class TestDynamicProxy { public static void main(String[] args) { RealSubjectA realA = new RealSubjectA(); SubjectA proxySubjectA = (SubjectA) Proxy.newProxyInstance(SubjectA.class.getClassLoader(), new Class[]{SubjectA.class}, new LogHandler(realA));//生成一个业务A的动态代理对象 RealSubjectB realB = new RealSubjectB(); SubjectB proxySubjectB = (SubjectB) Proxy.newProxyInstance(SubjectB.class.getClassLoader(), new Class[]{SubjectB.class}, new LogHandler(realB));//生成一个业务B的动态代理对象 proxySubjectA.setUser("heaven","123456"); proxySubjectB.sayHello("heaven"); }}
运行结果
begin to invoke method:setUser params:[heaven, 123456]-------------set user,name:heaven password:123456-------------invoke setUser endbegin to invoke method:sayHello params:[heaven]--------------say hello:heaven-------------invoke sayHello end
结果说明
1. 通过动态代理,我们的业务逻辑没有做任何修改便实现了日志打印功能,实现了解耦(这其实就是一个aop编程的例子)2. 我们没有为每个业务单独去写代理类,代理的代码量不会因为业务增加而庞大
Spring 框架中有一个事物代理可以让你提交/回滚一个事物,如果用动态代理的话,其方法调用序列如下:
web controller --> proxy.execute(...);proxy --> connection.setAutoCommit(false);proxy --> realAction.execute();realAction does database workproxy --> connection.commit();
转载地址:http://ghial.baihongyu.com/