1. 首页
  2. >
  3. 编程技术
  4. >
  5. Java

动态代理JDK和CGLib

我们在生活场景中处处都是代理:租房买房找中介、购买商品找商城、吃饭点外卖等。今天就和勾勾一起来学习代理设计模式,并通过代码理解静态代理和动态代理。


代理设计模式


代理模式(Proxy Pattern)就是当一个对象A对另一个对象B的访问无法或者不想直接访问时,可以在两个对象添加代理对象C,A通过访问C而间接的访问B对象。

动态代理JDK和CGLib

代理模式一般包含3个角色:

  • 抽象主题角色(ISubject):它可以是一个接口或者抽象类,主要是为了声明真实主题和代理主题的共同方法。
  • 真实主题角色(RealSubject):它是一个实现或者继承抽象主题的类,重写了抽象主题中定义的方法,也被称为被代理类或者目标类。
  • 代理类(Proxy):它具有真实主题的代理权,即内部有RealSubject的引用,客户端调用代理对象的方法也即是调用真实主题的方法,但是代理可以在真实主题的方法前后进行增强。

举个实际点的例子,假如我们作为消费者想购买一台手机,我们不会直接去找手机生产商去买,而是通过一些手机代理商(类似很多手机销售店或者网上商店)购买。在这个例子里面手机生产厂家是真实主题角色,也即是真正生产手机贩卖手机的角色,手机销售店即是代理角色,而我们作为消费者就可以通过代理角色访问到真实角色了。像一些店铺平时也会做满减活动,我们可以把它当作方法的增强。

通过上述的学习我们可以总结,代理模式的作用:一是为了保护真实主题对象,二是增强真实主题对象。

代理一般分为两类:静态代理和动态代理。

下面我们就通过代码实现上述买手机的例子。

首先我们先建一个抽象主题的接口,定义一个sell()的规范:

public interface Phone {     double sell(int amount); } 

再定义了两个真实主题:华为手机、小米手机。不要过多纠结这个amount,我们就认为它是1.

public class HWPhone implements Phone {         @Override     public double sell(int amount) {         return 4688.0;     } }  public class XMPhone implements Phone {         @Override     public double sell(int amount) {         return 2688.0;     } } 

我们首先通过静态代理实现代理模式。


静态代理


静态代理开发需要手动创建代理对象。那么我们接下来创建代理对象。

public class PhoneProxy implements Phone{     private Phone phone;      public PhoneProxy(Phone phone) {         this.phone = phone;     }      @Override     public double sell(int amount) {         double price = phone.sell(amount);         //对方法进行增强        System.out.println("满减活动");         return price;     } }  

接下来我们写个main方法,将其作为客户端消费者去买手机:

 public static void main(String[] args) {      PhoneProxy proxy = new PhoneProxy(new HWPhone());      double hwprice = proxy.sell(1);      System.out.println("消费者通过华为手机代理商购买一台手机的价格为:" + hwprice);       PhoneProxy proxy0 = new PhoneProxy(new XMPhone());      double xmprice = proxy0.sell(1);      System.out.println("消费者通过小米手机代理商购买一台手机的价格为:" + xmprice);   } 

通过上面代码我们可以发现静态代理实现起来是比较简单的,只需要都实现抽象主题接口就可以,但是依赖性比较强扩展起来不是很方便。

因此我们可以动态代理,动态代理分为JDK动态代理和CGLib动态代理。


动态代理-JDK


JDK动态代理是利用JDK的反射机制,创建代理对象,并且动态的指定要代理的目标类。

JDK动态代理使用java.lang.reflect包下的3个类:InvocationHandler、Method、Proxy。

  • InvocationHandler:调用处理器,定义了invoke方法。它用来表示代理具体要做什么,我们需要创建类实现InvocationHandler并且重写invoke方法。invoke需要3个参数:proxy:代理对象,无需赋值method:目标类中的方法。args:目标类中方法需要的参数。
public interface InvocationHandler {     public Object invoke(Object proxy, Method method, Object[] args)         throws Throwable; } 
  • Method,目标类中的方法,通过method.invoke(目标类,args)可以执行目标类的具体的方法。
  • Proxy:核心对象,用于创建代理对象。它通过调用newProxyInstance方法创建代理对象,我把这个方法的源码拿出来了,删除了无关紧要的代码,只看其中的关键代码,newProxyInstance它需要3个参数:ClassLoader:代理对象的类加载器interfaces:代理对象实现的接口InvocationHandler:我们自己创建的实现了InvocationHandler接口的类
public static Object newProxyInstance(ClassLoader loader,                                       Class<?>[] interfaces,                                       InvocationHandler h)     throws IllegalArgumentException {          final Class<?>[] intfs = interfaces.clone();        /*      * 查找或者生成代理对象      */     Class<?> cl = getProxyClass0(loader, intfs);        /*      * 获取InvocationHandler的构造器      */     final Constructor<?> cons = cl.getConstructor(constructorParams);     final InvocationHandler ih = h;       /*      * 根据构造器生成实例对象并返回      */       return cons.newInstance(new Object[]{h});     } 

了解了基本的概念之后,你可能会有点晕,那我们接下来使用JDK动态代理实现上述的功能,你可以在写完代码之后再来了解上面的知识。

首先创建MyInvocationHandler类实现InvocationHandler接口,重写invoke方法,在invoke方法中完成代理对象的真正业务逻辑:

public class PhoneProxyByJdk implements InvocationHandler {      Object target = null;     public PhoneProxyByJdk(Object target) {         this.target = target;     }      /**      * 获取代理类      * @param clazz      * @param <T>      * @return      */     public <T> getProxy(Class clazz) {         return (T) Proxy.newProxyInstance(clazz.getClassLoader(),                 new Class[]{clazz},                 this);     }      @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         Object res = method.invoke(target, args);         //方法增强         System.out.println("满减活动");         return res;     } }  

创建main方法模拟客户端消费者,消费者明确知道买哪个手机,创建这个厂家的对象并告诉InvocationHandler我要买这个厂家的手机,这个时候再使用Proxy创建代理对象,通过代理调用目标方法即可实现相同的效果。

public static void main(String[] args) {      //打印JDK动态代理字节码      System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles" , "true");     /**      * 消费者明确知道要买哪个手机,要找哪个代理商      */     PhoneProxyByJdk phoneProxyByJdk = new PhoneProxyByJdk(new HWPhone());     Phone proxy = phoneProxyByJdk.getProxy(Phone.class);     double price = proxy.sell(1);     System.out.println("消费者通过华为手机代理商购买一台手机的价格为:" + price);   }  

通过上面代码我们可以看到,JDK动态代理无需创建多个代理类,使用比较灵活。

我们分析其实现原理,我们在工作空间中打印了字节码文件:

动态代理JDK和CGLib

我们看到JDK动态代理对象实现了Phone目标接口,重写了目标方法。


动态代理-CGLib


CGLib是ASM框架生成的字节码,使用字节码技术生成代理类,效率比较高。

我们接下来继续使用CGLib动态代理实现动态代理。CGLib动态代理是第三方的工具库。

可以通过链接查看CGLib类库:http://cglib.sourceforge.net/apidocs/index.html

我们先引入CGLib的依赖:

<dependency>       <groupId>cglib</groupId>       <artifactId>cglib</artifactId>       <version>3.3.0</version> </dependency> 

创建MyMethodInterceptor类实现MethodInterceptor接口,重写了intercept方法,在这个方法里完成了代理类的所有逻辑。

public class PhoneProxyByCGLib implements MethodInterceptor {     /**      * 获取代理类      * @param clazz      * @param <T>      * @return      */     public <T> getProxy(Class clazz) {         //字节码增强类         Enhancer enhancer = new Enhancer();         //设置超类         enhancer.setSuperclass(clazz);         //设置回调类         enhancer.setCallback(this);         return (T) enhancer.create();     }      @Override     public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {         Object res = proxy.invokeSuper(target, args);         System.out.println("满减活动");         return res;     } } 

依然用main方法模拟客户端:

 public static void main(String[] args) {       //打印CGlib生成的字节码文件         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:/");         PhoneProxyByCGLib phoneProxyByCGLib = new PhoneProxyByCGLib();         Phone hwPhone = phoneProxyByCGLib.getProxy(HWPhone.class);         double price = hwPhone.sell(1);         System.out.println("消费者通过华为手机代理商购买一台手机的价格为:" + price);  } 

那么接下来我们分析下CGLib的原理,在main方法中我们打印了CGLib生成的字节码文件:

动态代理JDK和CGLib

找到HWPhone的代理对象字节码:

动态代理JDK和CGLib

这里我们看到了CGLib动态代理生成的代理对象是继承了我们的目标类,重写了目标类的方法,因此我们在调用目标类的方法时使用的是invokeSuper方法。

如果被代理的对象是个接口,CGLib动态代理生成的代理类是实现目标接口


总结


代理模式降低了系统之间的耦合性,可以保护目标对象并且可以增强目标对象,但是当类比较多的时候,也增加了代码的复杂性。

静态代理需要手动实现代理,代码编写简单,但是扩展性不好。

动态代理采用动态生成代码的方式,虽然复杂一点,但是使用起来更为灵活,动态代理有JDK动态代理和CGLib动态代理。

CGLib和JDK动态代理的对比:

  • JDK动态代理实现了被代理对象的接口,CGLib动态代理继承了被代理对象。
  • JDK动态代理是通过反射机制实现的,CGLib动态代理是通过增强字节码实现的,CGLib动态代理的执行效率更高
  • CGLib动态代理更复杂一点,生成代理类比JDK动态代理效率低一点。

动态代理在源码如Spring和Mybatis中是比较重要的,也是比较常见的面试题,希望这篇文章对你有帮助。