JDK动态代理与CGLib动态代理
代理
场景引入:以明星-经纪人为例
现在明星想要去开一个演唱会,但是演唱会需要提前布置现场,卖票等,然后才是明星上台演出,演出完需要收拾现场等一些收尾工作。
而我们知道,明星的主要业务就是唱歌、跳舞等,对于开演唱会前的工作以及演唱会结束后的工作,不在明星的核心业务里,那这些工作怎么办?
所以就有了经纪人,开演唱会前的工作就可以交给经纪人去处理,当处理完成就可以让明星去唱歌、跳舞,之后经纪人在处理演唱会结束后的工作。
但是,又有一个问题,经纪人只知道要做一些前置以及后置工作,但是不知道明星是要唱歌还是跳舞?
因此,我们把明星要做的事情抽取出来作为一个接口,这样,我们经纪人只要实现了该接口,就知道明星要做什么。
根据该场景,我们就可以获得一个类图:
静态代理
在代码编译前,代理类就已经写好了。
- 演出接口:
package base.proxy.staticproxy; // 演出接口 public interface Show { void sing(); void dance(); }- 明星类:
package base.proxy.staticproxy; public class Star implements Show { @Override public void sing() { System.out.println("明星开始唱歌..."); } @Override public void dance() { System.out.println("明星开始跳舞..."); } }- 经纪人类:
package base.proxy.staticproxy; public class BrokerProxy implements Show { private Show target; //经纪人代理的明星 public BrokerProxy(Show target) { this.target = target; } @Override public void sing() { System.out.println("经纪人做的前置工作:布置场地..."); target.sing(); System.out.println("经纪人做的后置工作:收尾工作..."); } @Override public void dance() { System.out.println("经纪人做的前置工作:布置场地..."); target.dance(); System.out.println("经纪人做的后置工作:收尾工作..."); } }- 测试类:
package base.proxy.staticproxy; public class Demo { public static void main(String[] args) { Star star = new Star(); BrokerProxy proxy = new BrokerProxy(star); proxy.sing(); proxy.dance(); } }可以看到,静态代理虽然简单,但是,我们需要自己编写代理类,而且一个目标类就得有一个代理类,而且,当接口方法增加时,需要修改的类太多,很难维护,所以就有了之后的动态代理。
JDK动态代理
JDK动态代理要求被代理的目标类必须实现接口
示例代码
- 定义接口(UserServiceInterface.java)
package base.proxy.jdk; /** * UserService接口 * 接口内声明需要代理的所有方法(默认是public abstract修饰) */ public interface UserServiceInterface { void sayHello(); }- 创建目标类并实现接口(UserService.java)
package base.proxy.jdk; /** * 目标对象:必须实现接口 */ public class UserService implements UserServiceInterface{ @Override public void sayHello() { System.out.println("hello"); } }- 创建代理类实现 InvocationHandler 接口(JDKProxyInvoker.java)
package base.proxy.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * jdk动态代理类: * 主要职责:根据传入的目标对象,调用newProxyInstance创建对应的代理对象 * 1. 需要实现InvocationHandler接口 * 2. 重写invoke方法 * 3. 通过Proxy类的newProxyInstance方法创建代理对象,返回代理对象(该过程可通过重载newProxyInstance) */ public class JDKProxyInvoker implements InvocationHandler { //要代理的对象 private Object target; /** * 重载newProxyInstance * 形参: * loader —— 类加载器,定义代理类 * interfaces —— 代理类要实现的接口列表 * h – 调用处理程序,用于将方法调用分发到 * 返回值: * 一个代理实例,带有指定的调用处理程序,该代理类由指定的类加载器定义,并实现指定的接口 * 返回值为Object类型,可根据实际强转为目标类的接口类型,通过其去调用相应方法 */ public Object newProxyInstance(Object target){ this.target = target; //返回回去的代理对象会拥有目标类接口的所有方法,并可对所有方法可执行下方重写的invoke增强方法 return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } /** * 来自接口: InvocationHandler * 在代理实例上处理方法调用并返回结果。当调用程序调用该方法时,该方法会被调用到其所关联的代理实例。 * 当代理对象调用相应的接口方法时,会调用invoke方法. * 形参: * proxy —— 方法被调用的代理实例 * method —— Method 对应代理实例调用的接口方法实例。该对象的 Method 声明类将是方法被声明的接口,这可能是代理类继承该方法的代理接口的超界面。 * args– 包含代理实例调用方法中传递参数值的对象数组,或者null如果接口方法不接受参数。原始类型的参数会被包裹在适当的原始包装类实例中,例如java.lang.Integerjava.lang.Boolean或。 * 返回值: * 从代理实例调用方法返回的值。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //编写增强逻辑 System.out.println("增强逻辑代码..."); Object result = method.invoke(target, args); System.out.println("增强逻辑代码结束..."); return result; } }- 使用代理对象(JDKDynamicProxyDemo.java)
package base.proxy.jdk; public class JDKDynamicProxyDemo { public static void main(String[] args) { //创建目标对象 UserService target = new UserService(); //创建代理对象 JDKProxyInvoker invoker = new JDKProxyInvoker(); /* **代理对象只能被转换为接口类型(如 UserServiceInterface)** 不能转换为具体的实现类类型(如 UserService) 这是因为 JDK 动态代理生成的代理类是实现接口,而不是继承具体类 */ UserServiceInterface proxy = (UserServiceInterface) invoker.newProxyInstance(target); //调用代理对象方法 proxy.sayHello(); } }核心流程
开始 ↓ ① 定义接口 (UserServiceInterface) ↓ ② 目标类实现接口 (UserService) ↓ ③ 创建 InvocationHandler 实现类 (JDKProxyInvoker) ├─ 持有目标对象引用 ├─ 重写 invoke 方法(定义增强逻辑) └─ 提供 newProxyInstance 方法 ↓ ④ 客户端调用 ├─ 创建目标对象 ├─ 创建 InvocationHandler 实例 ├─ 调用 newProxyInstance 生成代理对象 │ ↓ │ Proxy.newProxyInstance 内部: │ - 动态生成代理类($Proxy0) │ - 代理类实现指定接口 │ - 关联 InvocationHandler └─ 通过代理对象调用方法 ↓ 触发 invoke 方法执行 ├─ 前置增强 ├─ 调用目标方法 (method.invoke) └─ 后置增强想查看生成的代理类,可以对当前运行配置进行修改:
- 添加虚拟机选项
- java9+:-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
- 再次运行程序即可看到当前项目目录下生成了一个jdk包包下就是动态生成的代理类
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package jdk.proxy1; import base.proxy.jdk.UserServiceInterface; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements UserServiceInterface { //所有被代理的方法都会作为一个静态变量 private static final Method m0; private static final Method m1; private static final Method m2; private static final Method m3; public $Proxy0(InvocationHandler var1) { super(var1); } public final int hashCode() { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final boolean equals(Object var1) { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void sayHello() { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { //代理类会在静态代码块中通过反射把所有方法拿到静态变量中 try { m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("base.proxy.jdk.UserServiceInterface").getMethod("sayHello"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(((Throwable)var2).getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(((Throwable)var3).getMessage()); } } private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException { if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) { return MethodHandles.lookup(); } else { throw new IllegalAccessException(var0.toString()); } } }
关键要点
- 必要条件:目标类必须实现接口
- 代理对象类型:只能转换为接口类型,不能转换为具体类
- 创建代理对象的三个核心参数:
- ClassLoader:类加载器
- interfaces:代理类实现的接口
- InvocationHandler:方法调用处理器(InvocationHandler实现类)
- 方法拦截:所有通过代理对象调用的方法都会被 invoke 方法拦截
- 增强时机:可以在目标方法调用前后添加任意增强逻辑
CGLIB动态代理
CGLIB不需要接口,而是基于继承方式实现的动态代理,因此,被代理类以及被代理的方法不能是final。
示例代码
- 引入依赖
你平时开发用 Spring/SpringBoot中,Spring 核心包已经内置了 CGLIB
纯Java项目需要单独引入cglib的依赖 <!-- CGLIB 核心依赖 --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> <!-- 最新稳定版 --> </dependency>- 编写目标类(UserService.java):无需实现任何接口,普通的 Java 类即可
package cglib; /** * 目标对象(无需实现任何接口) */ public class UserService { public void sayHello() { System.out.println("hello"); } }- 编写代理类 (MyCGLIBInterceptor.java):实现 MethodInterceptor 接口,定义增强逻辑
package cglib; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 自定义回调拦截器 * MethodInterceptor 是 Enhancer 支持的一种 Callback 类型,用于在方法调用前后进行拦截增强。 */ public class MyCGLIBInterceptor implements MethodInterceptor { @Override public Object intercept( Object o, //代理对象本身(CGLIB生成的子类实例) Method method, //当前被拦截的目标方法(java.lang.reflect.Method类型) Object[] objects, //方法参数 /* CGLIB特有的MethodProxy对象,封装了对父类方法的快速调用方式, 通过invokeSuper直接调用,避免反射。 重点:methodProxy是CGLIB效率高的关键。它不像JDK那样依赖反射, 而是通过字节码直接调用父类方法。 */ MethodProxy methodProxy ) throws Throwable { System.out.println("增强逻辑代码..."); /* invokeSuper: 调用代理类的父类(目标类)的方法,CGLIB 官方推荐、无反射、高性能、不会死循环; invoke: JDK 原生反射调用,传错对象就会无限死循环(当传入代理对象自己就会死循环),性能更差。 */ Object result = methodProxy.invokeSuper(o, objects); System.out.println("增强逻辑代码结束..."); return result; } }- 创建目标类的代理对象并调用方法(CGLIBdynamicProxyDemo.java)
package cglib; import net.sf.cglib.proxy.Enhancer; public class CGLIBdynamicProxyDemo { public static void main(String[] args) { /* Enhancer类: 官方:生成动态子类以实现方法拦截。该类最初是JDK 1.3自带的标准动态代理支持的替代品, 但允许代理扩展一个具体的基类,同时实现接口。动态生成的子类覆盖了超类的非最终方法, 并带有钩子回调到用户自定义拦截器实现。最原始且最通用的回调类型是 MethodInterceptor 简言之: Enhancer 是 CGLIB 的核心类,相当于"代理类生成器"。 它内部封装了 ASM 字节码操作逻辑,负责帮你动态生成一个类的子类。 */ Enhancer enhancer = new Enhancer(); /* 设置所要代理的类: CGLIB会基于该类在运行时生成一个子类(比如叫 UserService$$EnhancerByCGLIB$$xxx), 该子类会拥有所有所有非 final 方法。(final不可被继承、修改) 注意: 如果 UserService 是 final 类,或者 sayHello() 是 final 方法, CGLIB 会报错或代理失败,因为 final 不能被继承/重写。 */ enhancer.setSuperclass(UserService.class); /* 设置回调拦截器: 生成的代理子类虽然重写了父类的方法,但重写后的方法体里不能写死逻辑, 它必须回调(Callback)到你指定的拦截器里,由你决定要不要增强、怎么增强。 因此,此处应该将我们自定义的回调拦截器对象传入 */ enhancer.setCallback(new MyCGLIBInterceptor()); /* 创建目标类的子类对象(属于向上转型),该子类对象就是代理对象 父类中的方法被子类重写:增加了拦截逻辑 核心流程: 生成字节码: Enhancer 使用 ASM 技术在内存中动态生成一个 UserService 的子类 .class 文件(字节码)。 加载类: 通过 ClassLoader 将这个新生成的类加载到 JVM 中。 实例化: 通过反射调用构造方法创建对象。 注意:CGLIB不使用反射指的是在方法调用时不使用反射,而是直接调用子类方法。 但是在创建对象时,CGLIB会通过反射调用子类的构造方法 赋值: 返回的对象实际上是子类实例,但可以用父类类型接收(多态)。 */ UserService proxy = (UserService) enhancer.create(); //调用代理对象方法(调用子类方法:继承于父类) proxy.sayHello(); /* 直接启动会报错: java.lang.reflect.InaccessibleObjectException: module java.base does not "opens java.lang" to unnamed module @7506e922 问题原因: Java 9+ 模块系统限制了反射访问 java.lang 包中的类和方法 解决方法: 添加 JVM 参数 --add-opens java.base/java.lang=ALL-UNNAMED */ } }如果想查看生成的代理类,可以修改当前文件/修改配置来实现:
以修改当前文件为例:在当前测试类的main方法开头加上即可实现
public static void main(String[] args) { // 把生成的代理类保存到指定目录 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./jdk/cglib"); //其他逻辑不变 ... }
核心流程
关键要点
- 继承关系:代理类是目标类的子类(基于继承)
- 方法重写:重写所有非 final 方法进行拦截
- invokeSuper:通过 MethodProxy 直接调用父类方法,无反射,高性能
- 注意事项:不能代理 final 类或 final 方法
JDK与CGLIB动态代理总结
JDK 动态代理是一种“基于接口”的运行时代理技术。它的必要条件是目标类必须实现至少一个接口。代理的创建围绕三个关键角色展开:你定义的接口、实现该接口的目标类,以及一个实现了 InvocationHandler 接口的调用处理器。运行时,Proxy.newProxyInstance() 方法会动态生成一个实现了所有目标接口的新类(即代理类),并将所有的方法调用都委托给你自定义的 invoke 方法。这意味着,你可以在 invoke 方法中对目标方法的调用进行统一拦截和增强(如添加日志、事务等),而无需修改原始类的代码。最终获得的代理对象,其类型只能是接口类型,这确保了面向接口编程的纯粹性,也是其与基于继承的代理方案的本质区别。
CGLIB动态代理是一种不依赖于接口,而是通过字节码生成技术在运行时动态创建目标类子类来实现代理的机制。其核心在于继承——代理类直接继承自目标类,通过重写父类的非final方法,并委托给自定义的MethodInterceptor实现类,通过重写intercept方法来注入增强逻辑。这使得它可以代理普通类,打破了JDK动态代理必须实现接口的限制。
关键要点与流程清晰明了:首先,使用核心工具类Enhancer指定目标类作为父类(setSuperclass)和配置回调拦截器(setCallback);随后,Enhancer会利用ASM框架在内存中生成目标类的子类字节码,并通过类加载器将其加载进JVM;最终通过反射调用构造方法来实例化得到代理对象。当调用代理对象的任何非final方法时,控制权都会流转到MethodInterceptor.intercept()方法中。在这里,可以自定义前置或后置增强逻辑,并通过MethodProxy.invokeSuper()高效地调用原始父类方法,此调用直接基于方法索引而非反射,是CGLIB性能优于反射调用的关键所在。
需要注意的是,由于其基于继承的特性,CGLIB无法代理被声明为final的类或方法。同时,在Java 9及以上版本,由于模块系统的限制,可能需要额外的JVM参数(如--add-opens)来允许对java.base模块的深层反射访问,以确保字节码生成过程顺利进行。
