JAVA 代理模式
简介
代理模式的核心是代理类,给某一个真实对象提供一个代理对象,并由代理对象来控制对真实对象的访问。
代理模式是一种结构型设计模式。
代理模式分为两类:静态代理、动态代理。
简单来说,代理模式就是创建一个代理对象来代替真实对象做事。
所谓代理,就是代替的意思,代替雇主做事。
比如明星和经纪人的关系,明星就是真实对象,经纪人就是代理对象。
静态代理
调用代理对象(ProxyObject)重写接口的方法时,实际上执行的是目标对象(TargetObject)的方法。
模式中存在三种角色:
Subject:抽象主题角色,可以是抽象类(Abstract)也可以是接口(interface),这是一个最普通的业务类型定义,其中定义的方法交给实现子类和代理类实现。
RealSubject:真实主题角色,就是真实对象(TargetObject),被代理的对象,是业务真实执行者。
Proxy:代理主题角色,也叫委托类、代理对象(ProxyObject)。用来代替真实对象的访问。
目标对象是实现一个接口的目标对象。
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护
针对以上缺陷,可以使用动态代理方式

以购物为例,在执行购物方法前后都打印一句话。
interface ShoppingService { void go(); } public static class ShoppingServiceImpl implements ShoppingService { @Override public void go () { System.out.println("正在购物呢"); } } public static class ShoppingServiceProxy implements ShoppingService { private final ShoppingService service; public ShoppingServiceProxy (ShoppingService service) { this.service = service; } @Override public void go () { System.out.println("准备去购物啦"); service.go(); System.out.println("购物完回家咯"); } } public static void main (String[] args) { // 真实对象 ShoppingService service = new ShoppingServiceImpl(); // 代理对象 ShoppingServiceProxy serviceProxy = new ShoppingServiceProxy(service); serviceProxy.go(); }
动态代理
动态代理有两种:JDK 动态代理、cglib 动态代理。
动态代理是通过字节码操作框架,在 JVM 运行时,通过目标接口或对象,计算出代理类的字节码,
然后加载到 JVM 中使用,其调用目标对象的方法也是通过 Java 反射实现。
对比一下,静态代理不会自动生成额外的代理对象,所有的对象都是用户在显式操作,性能上与普通模式没有区别。
而动态代理需要自动生成额外的字节码代理对象,调用也是通过 Java 反射,性能上略有影响。
JDK 动态代理
目标对象是实现一个接口的实现类对象,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
其实际是调用代理对象(ProxyObject)的方法时,使用反射调用目标对象(TargetObject)的方法。
代理对象与静态代理一样都是代理接口的实现类,只不过是由 JVM 运行时动态创建的。
InvocationHandler 和 Proxy 是 JDK 动态代理最核心的部分,
Proxy 提供了创建动态代理类和实例的静态方法,InvocationHandler 是由代理实例的调用处理程序(即真实对象)实现的接口。
每个代理实例都有一个关联的调用处理程序(被代理的真实对象)。
当在代理实例上调用方法时,方法调用被编码并分派到其调用处理程序的 invoke方法(使用反射调用真实对象的方法)。

以恰饭为例,恰饭前后打印一句话。
interface Service { void help(); } public static class ServiceImpl implements Service { @Override public void help () { System.out.println("正在恰饭"); } } public static class DynamicProxy implements InvocationHandler { // 真实代理对象 -> Service 实现类 private final Object service; // DynamicProxy 代理 Service 实现类 public DynamicProxy (Object service) { this.service = service; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("马上要恰饭咯"); method.invoke(service, args); System.out.println("恰完饭了呢"); return null; } } public static void main (String[] args) { Service service = new ServiceImpl(); // 写入真实代理对象 InvocationHandler handler = new DynamicProxy(service); // 创建代理对象 // Classloader -> 指定当前目标对象使用类加载器,获取加载器的方法是固定的 // Interface -> 目标对象实现的接口的类型,使用泛型方式确认类型 // InvocationHandler -> 事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入 Service serviceProxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler); // 调用代理对象方法 serviceProxy.help(); }
CGLib 代理
也叫作子类代理(FastClass 的子类并不是委托类的子类),动态生成子类方式是,
解析委托类字节码,
使用字节码处理框架 ASM 向生成类中写入委托类实例直接调用方法的语句,
并为委托类通过方法签名和参数类型建立索引,
调用时通过索引调用委托类方法。
使用这种模板方式可以解决 Java 语法不支持的问题,同时改善 Java 反射性能。
相比其他代理方式,cglib 可以代理普通的类,无论目标类是否实现接口。
需要注意的是,委托类和委托类方法不能是 final 类型。
Enhancer 和 MethodInterceptor 是整个代理过程的核心。
Enhancer 用于生成基于委托类字节码的 FastClass 子类,
MethodInterceptor 拦截对于委托类的调用,将调用方法签名解析为索引,再通过索引调用生成类中对于委托类方法的调用。

相关演示代码如下
/** * <p style="color:rgb(0,255,0);">委托类或者说被代理类</p> **/ public static class Meal { public void eat() { System.out.println("干饭人淦饭"); } } /** * <p style="color:rgb(0,255,0);">方法拦截器</p> * 用户调用代理类的方法将在此被拦截,实现对目标方法的调用、参数修改以及功能增强 **/ public static class MealInterceptor implements MethodInterceptor { /** * <p style="color:rgb(255,165,0);">方法描述</p> * @param o 委托对象 * @param method 委托方法或者说拦截方法 * @param objects 方法参数 * @param methodProxy 可用于调用原始方法,或在相同类型的不同对象上调用相同方法。 * @return 返回代理方法的返回值 */ @Override public Object intercept (Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 调用前增强 System.out.println("马上要干饭咯"); // 调用指定对象的原始(超级)方法。 methodProxy.invokeSuper(o, objects); // 调用后增强 System.out.println("干饭完了呢"); return null; } } public static void main (String[] args) { // Enhancer -> 生成动态子类以启用方法拦截, 动态生成的子类覆盖超类的非最终方法,并具有回调到用户定义的拦截器实现的钩子。 Enhancer enhancer = new Enhancer(); // 设置超类,或者说父类 enhancer.setSuperclass(Meal.class); // 设置回调方法拦截器(MethodInterceptor),这里使用数组可设置多个拦截器 enhancer.setCallback(new MealInterceptor()); // 创建代理类并强制转换为超类 Meal meal = (Meal) enhancer.create(); // 调用代理类方法 meal.eat(); }
反编译代码
通过 JDK 工具可以捕获程序运行时,CGLib 生成的 FastClass 子类
/** Enhancer 基于委托类(Meal)生成的 FastClass 子类 */ public class CglibDynamicProxy$Meal$$FastClassByCGLIB$$94a6679 extends FastClass { // 超类(父类) FastClass 中的 invoke 方法,调用委托类(Meal)的委托方法(eat) // var1 -> 通过方法签名 + 参数类型列表计算出委托方法的索引 // var2 -> 委托类实例 // var3 -> 参数列表 public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException { Meal var10000 = (Meal)var2; int var10001 = var1; try { switch(var10001) { case 0: // 委托方法 var10000.eat(); return null; case 1: return new Boolean(var10000.equals(var3[0])); case 2: return var10000.toString(); case 3: return new Integer(var10000.hashCode()); } } catch (Throwable var4) { throw new InvocationTargetException(var4); } throw new IllegalArgumentException("Cannot find matching method/constructor"); } public CglibDynamicProxy$Meal$$FastClassByCGLIB$$94a6679(Class var1) { super(var1); } /** 根据构造方法索引调用委托类构造方法 var1 -> 方法索引 var3 -> 参数列表 */ public Object newInstance(int var1, Object[] var2) throws InvocationTargetException { Meal var10000 = new Meal; Meal var10001 = var10000; int var10002 = var1; try { switch(var10002) { case 0: var10001.<init>(); return var10000; } } catch (Throwable var3) { throw new InvocationTargetException(var3); } throw new IllegalArgumentException("Cannot find matching method/constructor"); } // 根据方法参数类型列表查询匹配构造函数的索引 public int getIndex(Class[] var1) { switch(var1.length) { case 0: return 0; default: return -1; } } // 通过方法签名 + 参数类型列表返回匹配方法的索引 public int getIndex(String var1, Class[] var2) { switch(var1.hashCode()) { case -1776922004: if (var1.equals("toString")) { switch(var2.length) { case 0: return 2; } } break; case -1295482945: if (var1.equals("equals")) { switch(var2.length) { case 1: if (var2[0].getName().equals("java.lang.Object")) { return 1; } } } break; case 100184: if (var1.equals("eat")) { switch(var2.length) { case 0: return 0; } } break; case 147696667: if (var1.equals("hashCode")) { switch(var2.length) { case 0: return 3; } } } return -1; } // 通过方法签名的包装类找到委托类方法索引 // Signature -> 方法签名的表示形式,包含方法名、返回类型和参数类型。 public int getIndex(Signature var1) { String var10000 = var1.toString(); switch(var10000.hashCode()) { case -1310345955: if (var10000.equals("eat()V")) { return 0; } break; case 1826985398: if (var10000.equals("equals(Ljava/lang/Object;)Z")) { return 1; } break; case 1913648695: if (var10000.equals("toString()Ljava/lang/String;")) { return 2; } break; case 1984935277: if (var10000.equals("hashCode()I")) { return 3; } } return -1; } // 返回这个类的最大方法索引 public int getMaxIndex() { return 3; } }