要理解动态代理,不妨先来看看一个静态代理的例子。
一.静态代理
以一个电商项目的例子来说明问题,比如我定义了一个订单的接口IOrder,其中有一个方法时delivery,代码如下。
package com.xdx.learn; public interface IOrder { void delivery();//发货 void confirmReceipt();//确认收货 }
Order类实现了该接口,如下所示。
package com.xdx.learn; public class Order implements IOrder { public void delivery() { System.out.println("delivering the commodity"); } public void confirmReceipt() { System.out.println("confirmReceipt the commodity"); } }
假如写完这个项目后,老板想要在order类的每个方法中加入操作日志的功能,或者性能统计。可是我又不想再更改原来的order类,特别是在接手别人的项目的时候,我们特别不喜欢去修改既有的代码,一方面修改容易导致未知的问题,另外一方面,修改有时候不如自己重写快。
此时我们可以为Order类写一个代理类,对原来的Order类进行一层简单的包装,以达到目的。
package com.xdx.learn; public class OrderProxy implements IOrder { private Order order;//注入原来的Order类 public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } public void delivery() { System.out.println("do some log");//添加一些关于日志的操作 order.delivery();//目标类(被代理类)的业务逻辑 } public void confirmReceipt() { System.out.println("do some log");//添加一些关于日志的操作 order.confirmReceipt();//目标类(被代理类)的业务逻辑 } }
调用代理类的方法如下所示。
public static void main(String args[]){ Order order=new Order();//目标类对象 OrderProxy proxy=new OrderProxy();//代理类对象 proxy.setOrder(order);//注入 proxy.delivery();//带有日志功能的发货操作 proxy.confirmReceipt();//带有日志功能的确认收货操作 }
从上面的例子可以看出,静态代理主要的好处是不需要修改原来的目标类,实现解耦。
但是假如目标类里面有很多方法呢?或者假如我要为更多的目标类添加日志功能呢?那我必须为每个类都定义一个静态代理类,并且为每个类的每个方法写一个对应的加入了日志管理功能的方法。显然这是一项巨大的工程。这时候静态代理已经不能满足我们的需求了,我们需要的是动态代理。
二.动态代理
首先我们需要对动态代理和静态代理的概念做一下解释,所谓的静态代理就是代理类在运行之前就写好了,比如我们上面写好的OrderProxy这个类。与之对应的,动态代理的 代理类对象是当程序运行的时候才产生的,也就是说我们事先并没有定义一个代理类,而是需要用到的时候它才产生。动态代理又分为两种,一种是基于jdk的,一种是基于Cglib的。他们的实现原理有所不同。
1.基于JDK的动态代理。
我简单的描述一下整个过程涉及到的各方类。
IOrder:接口
Order:目标类,我们称为target。也就是被代理的类。
然后我们需要一个实现了InvocationHandler接口的类OrderHander,在这个类中,我们生成一个代理对象并与该hander对象进行绑定,并且利用反射机制(主要是Method的invoke方法)来调用目标类的方法。且在方法中织入增强逻辑(例如日志管理功能)。这样说很抽象,我们来看实际的Hander类吧。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class OrderHander implements InvocationHandler { private Object target;// 目标类对象,即被代理的对象 public OrderHander(Object target) { this.target = target; } public Object Bind() { // 绑定操作,生成一个target的代理类对象,该代理类与目标类实现相同的接口,所以需要传入接口的参数。
,并且将该代理对象与this,也就是该OrderHander对象绑定 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /** * 该方法是InvocationHandler的唯一的方法,当与该OrderHander类对象绑定的代理类的方法被调用的时候, * 就会执行该方法,并且传入代理对象,方法对象,以及方法的参数。这样我们才可以用反射机制来调用目标类的方法。 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do some log"); method.invoke(target, args);// 注意这边的第一个参数target,即目标类,而非代理类。因为执行的是目标类的业务逻辑。
System.out.println(method); return null; } }
OrderHander 实现了InvocationHandler 接口,查看jdk源码,有一段是这样的。
/** * {@code InvocationHandler} is the interface implemented by * the <i>invocation handler</i> of a proxy instance. * * <p>Each proxy instance has an associated invocation handler. * When a method is invoked on a proxy instance, the method * invocation is encoded and dispatched to the {@code invoke} * method of its invocation handler.
简单翻译一下:InvocationHandler是一个接口,它被一个proxy instance(代理类对象)的invocation handler(调用 处理器)所实现。这句话虽简单,但是表述得很明确,invocation handler(调用 处理器)是属于某个代理类对象。这也是我们为什么在OrderHander 中将目标类的代理类与自身绑定的原因。
下一句话:每个代理对象都有一个相关联的,当一个方法被代理对象调用的时候,这个被调用的方法会被编码并且分发到与这个代理类对象关联的invocation handler(调用 处理器)的invoke方法处执行。
也就是说,代理类是依附于调用 处理器存在的,在调用它的方法的时候,并不会实际执行,而是转发到它所对应的调用处理器中的invoke方法执行。
这也解释了,我们在OrderHander类中的invoke方法中不仅能织入增强(日志管理),而且通过method.invoke()方法调用了目标类的实际方法。
我们来为上述调用处理器写一个main方法,证实我们的理论。
public static void main(String args[]){ Order order=new Order();//目标对象 System.out.println(order); OrderHander orderHander=new OrderHander(order);//调用处理器对象 IOrder orderProxy=(IOrder) orderHander.Bind();//生成代理对象,注意这里是IOrder对象,可以理解为多态吧,并且与orderHander绑定了 orderProxy.delivery(); orderProxy.confirmReceipt(); }
上述代码的执行结果如下:
com.xdx.learn.Order@15db9742
public abstract void com.xdx.learn.IOrder.delivery()
do some log
delivering the commodity
public abstract void com.xdx.learn.IOrder.confirmReceipt()
do some log
confirmReceipt the commodity
其中public abstract void com.xdx.learn.IOrder.delivery()和public abstract void com.xdx.learn.IOrder.confirmReceipt()是我在invoke方法中system.out.println(method)。可看到这个menthod对象是接口处method。这也解释得通为什么我们再获取一个代理对象的时候采用IOrder,即原始的接口类来定义。
我们可以近似简单的理解:代理类跟目标类是同一个接口的不同实现类,所以可以用多态的形式来调用代理类的方法的方法,而转发给其handle对象来执行,在hander对象的invoke方法中,我们除了织入增强,还调用了method.invoke()方法,因为此处的method对象是接口的方法对象,所以同样可以传入目标类target这个对象作为参数。
由此可见,基于jdk的动态代理依赖于接口的实现,这就要求我们必须为每个目标类都定义一个接口,才能采用这种代理方式。
2.基于Cglib的动态代理方式
基于Cglib的动态代理方式不是采用实现接口的方式去构造一个代理类,而是直接为代理类实现一个增强过后的子类,子类的生成运用了字节码技术,整个过程主要使用了Enhancer、MethodInterceptor、MethodProxy等类。
首先需要引入Cglib的相关jar包,maven配置如下。
<!-- 动态代理cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
下面还是以一个例子来阐述Cglib动态代理的原理。
目标类:Order类的代码如下:
package com.xdx.learn; public class Order { public void delivery(){ System.out.println("delivering the commodity"); } public void confirmRecipet(){ System.out.println("confirmRecipeting the commodity"); } }
新建一个CglibInterceptor,实现了MethodInterceptor接口,其代码如下所示。
package com.xdx.learn; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibInterceptor implements MethodInterceptor { // 增强器,它的作用是对目标类对象增强,生成一个代理类,并且指定一个回调对象,这个回调对象一般是一个MethodInterceptor对象。 // 通过MethodInterceptor对象的拦截功能,即intercept方法,我们可以做一些增强工作。 private Enhancer enhancer = new Enhancer(); // 以下方法生成一个代理对象,它以目标对象的子类的形式存在 public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz); enhancer.setCallback(this);// 设置当前MethodInterceptor对象为enhancer对象的回调对象,这个步骤至关重要 return enhancer.create();// 调用create()方法生成一个子类(代理类,增强类)。 } /** * 所有代理类调用的方法都会被这个方法所拦截,前提是该代理类设置的回调对象是该CglibInterceptor对象本身 * * @param obj * 代理类,也就是被增强的类 * @param method * 被拦截的方法 * @param args * 方法参数 * @param proxy * 用于调用目标类(也就是父类)的方法 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("do some log"); System.out.println(obj.getClass().getName()); System.out.println(method);// System.out.println("使用MethodProxy对象调用父类的方法"); proxy.invokeSuper(obj, args); return null; } }
Enhancer类可以对目标类进行增强,通过字节码技术生成目标类的一个子类对象,也就是代理类对象。并且指定了Callback对象,CallBack对象一般为MethodInterceptor类对象,在上述代码中,我们设为this。也就是当前CglibInterceptor对象。我们来看源码中关于
Enhancer类的两段描述。
/** * Generates dynamic subclasses to enable method interception. This * class started as a substitute for the standard Dynamic Proxy support * included with JDK 1.3, but one that allowed the proxies to extend a * concrete base class, in addition to implementing interfaces. The dynamically * generated subclasses override the non-final methods of the superclass and * have hooks which callback to user-defined interceptor * implementations. * <p> * The original and most general callback type is the {@link MethodInterceptor}, which * in AOP terms enables "around advice"--that is, you can invoke custom code both before * and after the invocation of the "super" method. In addition you can modify the * arguments before calling the super method, or not call it at all. * <p>
大概翻译:该类可以为目标类生成动态的子类,从而进行方法的拦截。它是基于JDK的动态代理的一种补充,弥补了JDK动态代理只能通过实现接口来创建代理类的不足。该动态生成的的子类重写了父类(目标类)的非finla方法(因为final方法是不能重写的),并且,它与用户自定义的拦截器(interceptor)之间通过callback方法产生了关联(hooks )。
最常用的callback类型是MethodInterceptor类的实例对象,MethodInterceptor对象在AOP语境下可以实现around advice(环绕增强),所谓的环绕增强就是在父类(目标类)方法的调用前后都可以加入自己的代码。
简而言之:Enhancer通过字节码技术生成目标类的子类(代理类),并且与一个MethodInterceptor对象产生了挂钩。
那MethodInterceptor对象又是怎么实现方法的增强的呢?我们还是去看看MethodInterceptor类的源码。
/** * General-purpose {@link Enhancer} callback which provides for "around advice". * @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a> * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $ */ public interface MethodInterceptor extends Callback { /** * All generated proxied methods call this method instead of the original method. * The original method may either be invoked by normal reflection using the Method object, * or by using the MethodProxy (faster). * @param obj "this", the enhanced object * @param method intercepted Method * @param args argument array; primitive types are wrapped * @param proxy used to invoke super (non-intercepted method); may be called * as many times as needed * @throws Throwable any exception may be thrown; if so, super method will not be invoked * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value. * @see MethodProxy */ public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; }
大概翻译:
类上的注释:该类主要目的是为Enhancer对象提供一个回调对象,用于方法的增强。这段话几乎是与Enhancer那边的说明遥相呼应的。
方法上的注释:所有调用代理类的方法都会被替换成调用该intercept方法,其实就是一个拦截的操作。可以通过普通的反射机制(Method方法)来调用原始方法,或者通过MethodProxy对象来调用原始方法,后者速度更快。
参数:obj:代理类对象,method:被拦截的方法,args:参数,proxy:一个MethodProxy对象,它是为了我们能调用目标类(父类)的方法。
这些参数都是Enhancer对象传递过来的,这样Enhancer就与MethodInterceptor实现了关联。
最后,我们为Cglib动态代理写一个调用方法,如下所示。
public static void main(String args[]){ CglibInterceptor interceptor=new CglibInterceptor(); Order order=(Order) interceptor.getProxy(Order.class);//生成一个增强对象,并且已经指定了interceptor为callback对象 order.delivery(); order.confirmRecipet(); }
上述代码的运行结果如下:
do some log
com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b
public void com.xdx.learn.Order.delivery()
delivering the commodity
do some log
com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b
public void com.xdx.learn.Order.confirmRecipet()
confirmRecipeting the commodity
可以看到确实实现了增强,也看到了增强后的代理类是com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b。
三.两种方法的比较
1.基于JDK的代理依附于接口,所以必须为每个目标类创建接口,这点比较麻烦。
2.基于CGLIB的动态代理采用子类的方式生成代理,并且它的性能会比基于JDK的代理来得高。但是在生成代理的时候会比较慢,所以如果需要代理的目标类是单例的情况下,推荐这种代理方式。
3.其实代理类都不真正去调用执行方法,而是交给第三方对象去调用执行方法,并且在执行的过程中织入增强。基于JDK的代理是交给InvocationHandler对象的invoke方法,而基于CGLIB的代理则是交给MethodInterceptor的intercept方法。前提是,二者都要在生成代理类的时候与相应的第三方对象产生关联。
4.二者都有使用局限,基于JDK的代理方式无法为非public方法,static方法实现增强(因为接口都是public方法,接口中也不能有static方法)。而CGLIb是通过覆盖父类方法来实现代理的,所以一切不能被重写的方法都无法被增强,即final,static,private方法都不能被增强。