跳到主要内容JDK 动态代理与 CGLIB 动态代理区别及 Spring AOP 适配原理 | 极客日志Javajava
JDK 动态代理与 CGLIB 动态代理区别及 Spring AOP 适配原理
JDK 动态代理基于接口实现,通过反射调用目标方法,要求目标类必须实现接口。CGLIB 动态代理基于继承实现,生成目标类的子类,利用 FastClass 索引化调用提升性能。在 Spring AOP 中,若目标类有接口优先使用 JDK 代理,若无接口则自动切换为 CGLIB 代理。CGLIB 因无需接口且高频调用性能更优,常被视为更适配复杂场景的代理方案,但需注意 final 类无法被代理的限制。
一、JDK 动态代理原理
package com.example.proxy;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
public class JdkDynamicProxyDemo {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxyObject = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
(proxyObj, method, argsArray) -> {
System.out.println("方法开始执行了...");
Boolean flag = (Boolean) method.invoke(userService, argsArray);
System.out.println("方法结束执行了...");
return flag;
}
);
boolean flag = proxyObject.login("admin", "123456");
System.out.println("flag = " + flag);
}
}
在 JDK 动态代理中,我们先创建一个包含真实业务逻辑的目标对象(如 UserServiceImpl),再通过 动态生成一个实现了相同接口的代理对象。这个代理对象本身不包含业务逻辑,它的核心是通过 拦截方法调用,在调用前后插入增强逻辑(如日志、事务),并通过 反射调用目标对象的真实方法。代理对象是多例的,每次调用 都会生成一个新实例,其生命周期由代码作用域决定,而非随单次方法调用结束而销毁。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Proxy.newProxyInstance()
InvocationHandler
method.invoke()
newProxyInstance
二、CGLIB 动态代理原理
- JDK 代理的
Method.invoke() 是纯反射调用,每次都要动态解析方法签名、检查权限,有固定开销;
- CGLIB 的
MethodProxy.invokeSuper() 基于 FastClass 生成索引化调用逻辑,绕过反射的动态解析,高频调用场景下性能比 JDK 代理高 20%-30%(Spring 5+ 对 JDK 代理做了优化,差距缩小,但 CGLIB 仍略优)。
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
CreateInfo ci = this.createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
private static FastClass helper(CreateInfo ci, Class type) {
FastClass.Generator g = new FastClass.Generator();
g.setType(type);
g.setClassLoader(ci.c2.getClassLoader());
g.setNamingPolicy(ci.namingPolicy);
g.setStrategy(ci.strategy);
g.setAttemptLoad(ci.attemptLoad);
return g.create();
}
MethodProxy 是 CGLIB 为代理方法生成的'高性能调用器',核心解决普通反射 Method.invoke() 性能低的问题。这段代码的核心流程是:创建 MethodProxy → 懒加载初始化 FastClass → 为方法分配索引 → 通过索引快速调用。
2.1 逐方法拆解源码
1. create(...) 方法:初始化 MethodProxy 的基础元信息
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
c1:目标类(如 UserService,真正包含业务逻辑的类);
c2:CGLIB 动态生成的代理类(如 UserService$$EnhancerByCGLIB$$xxxx,目标类的子类);
desc:方法的字节码描述符(如 login 方法的描述符是 (Ljava/lang/String;Ljava/lang/String;)Z,代表'两个 String 参数,返回 boolean');
name1:目标类中方法的原始名称(如 login);
name2:代理类中对应方法的别名(CGLIB 生成的,如 CGLIB$login$0);
sig1/sig2:Signature 是 CGLIB 封装的'方法签名'(方法名 + 描述符),用于唯一标识一个方法;
createInfo:临时存储目标类/代理类的核心信息(类加载器、命名规则等),供后续生成 FastClass 使用。
核心作用:为目标方法和代理方法做'身份标记'——相当于给方法办了'身份证',后续生成 FastClass 时,能通过这个'身份证'找到对应的方法。
2. init() 方法:懒加载创建 FastClass(性能优化的核心)
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
CreateInfo ci = this.createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
- 懒加载:init() 不会在 create() 时执行,而是第一次调用
methodProxy.invokeSuper() 时才触发——如果代理方法从未被调用,就不用生成 FastClass,节省内存和初始化时间;
- 双重检查锁(DCL):保证多线程下只会生成一次 FastClass,避免重复创建导致的性能损耗;
- FastClass 索引化:
fci.f1.getIndex(this.sig1) 是核心 ——FastClass 会为目标类的所有方法生成一个'索引表'(方法名 → 整数索引),比如 login 方法的索引是 1,logout 是 2。后续调用方法时,只需传索引 1,不用再通过方法名反射找方法,这是性能提升的关键;
fastClassInfo:缓存 FastClass 和方法索引,后续调用直接复用,无需重复生成。
3. helper(...) 方法:生成 FastClass 实例
private static FastClass helper(CreateInfo ci, Class type) {
FastClass.Generator g = new FastClass.Generator();
g.setType(type);
g.setClassLoader(ci.c2.getClassLoader());
g.setNamingPolicy(ci.namingPolicy);
g.setStrategy(ci.strategy);
g.setAttemptLoad(ci.attemptLoad);
return g.create();
}
FastClass 的本质:FastClass 是 CGLIB 为目标类/代理类生成的'快速调用类',它的核心逻辑是:
public class UserService$$FastClassByCGLIB$$xxxx {
public Object invoke(int index, Object obj, Object[] args) {
UserService target = (UserService) obj;
switch(index) {
case 1: return target.login((String)args[0], (String)args[1]);
case 2: return target.logout();
}
}
}
可以看到:FastClass 把'通过方法名反射调用'变成了'通过整数索引直接调用',完全是普通的 Java 方法调用,没有任何反射开销。
结合调用流程,看懂性能优化的本质
当你调用 methodProxy.invokeSuper(proxyObj, args) 时,底层执行流程是:
methodProxy.invokeSuper(...) → 触发 init()(首次调用) → helper() 生成目标类/代理类的 FastClass → 为目标方法分配索引(如 login 方法索引=1) → 调用 FastClass.invoke(1, proxyObj, args) → 通过 switch(index) 直接调用目标方法(无反射)
2.2 代码示例
package com.example.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
class UserService {
public boolean login(String username, String password) {
System.out.println("UserService - login 目标方法执行了");
return "admin".equals(username) && "123456".equals(password);
}
}
public class CglibDynamicProxyDemo {
public static void main(String[] args) {
UserService target = new UserService();
UserService proxy = (UserService) Enhancer.create(
UserService.class,
(MethodInterceptor) (proxyObj, method, argsArr, methodProxy) -> {
System.out.println("Before " + method.getName() + ": " + argsArr[0]);
Object result = methodProxy.invoke(target, argsArr);
System.out.println("After " + method.getName() + ": " + result);
return result;
}
);
boolean flag = proxy.login("admin", "123456");
System.out.println("flag = " + flag);
}
}
内部 Enhancer e = new Enhancer(); e.setSuperclass(type); e.setCallback(callback); e.create();
CGLIB 动态代理通过 Enhancer 核心工具类实现,无需目标类实现接口,而是以目标类(如 UserService)作为父类动态生成子类作为代理类;Enhancer.create 方法指定目标类 Class 对象和 MethodInterceptor 回调接口,代理类的所有方法调用都会被该回调拦截,回调方法接收代理实例、被拦截方法的反射对象、方法实际参数值和 CGLIB 优化的 methodProxy 对象;调用代理对象方法时,会先执行前置增强逻辑,再通过 methodProxy.invoke(性能优于普通反射)调用目标对象的核心业务方法,执行完成后执行后置增强逻辑并返回结果,这一机制弥补了 JDK 动态代理需实现接口的局限性,同时通过方法代理优化提升了调用性能。
三、对比
| 对比维度 | JDK 动态代理(JDK Proxy) | CGLIB 动态代理(Code Generation Library) |
|---|
| 底层原理 | 基于接口实现:动态生成一个实现目标接口的代理类(如 $Proxy0),代理类的方法转发给 InvocationHandler | 基于继承实现:动态生成目标类的子类作为代理类,重写目标方法并转发给 MethodInterceptor |
| 核心限制 | 必须要求目标类实现至少一个接口(否则无法生成代理) | 目标类不能是 final(无法继承),目标方法不能是 final(无法重写) |
| 核心类 / 接口 | Proxy.newProxyInstance()、InvocationHandler | Enhancer.create()、MethodInterceptor、MethodProxy |
| 方法调用方式 | Method.invoke()(纯反射,性能中等) | MethodProxy.invokeSuper()(FastClass 索引调用,性能更高) |
| 侵入性 | 目标类必须实现接口(侵入式设计) | 目标类无需实现任何接口(无侵入式设计) |
| 实例化方式 | 代理类实现接口,可直接强转为接口类型 | 代理类继承目标类,可直接强转为目标类类型 |
| Spring AOP 默认选择 | 目标类有接口 → 优先用 JDK 代理 | 目标类无接口 → 自动切换为 CGLIB 代理 |