Spring AOP 底层默认支持两种代理模式:JDK 动态代理 和 CGLIB 动态代理。很多同学只知道 Spring 会自动选择代理方式,却不清楚两者的区别、适用场景,面试时被问到就卡壳;甚至在项目中遇到代理失效的问题,也不知道如何排查。
一、什么是 Spring AOP 代理?
在讲区别之前,我们先明确一个核心概念:Spring AOP 的本质是「动态代理」,简单来说,就是 Spring 会在运行时,动态生成一个「代理对象」,这个代理对象会包裹住目标对象(我们的业务对象),当我们调用目标对象的方法时,实际上是先调用代理对象的方法,代理对象在方法执行前后,插入增强逻辑(如日志、限流),最后再执行目标对象的核心业务逻辑。
举个通俗的例子:你去房产中介买房,中介就是「代理对象」,你(目标对象)的核心需求是「买房」,而中介会在你买房前后,帮你做很多增强操作(找房源、谈价格、办过户),这些增强操作不影响你买房的核心需求,却能帮你省去很多麻烦------这就是代理模式的核心价值:解耦增强逻辑与业务逻辑。
Spring AOP 之所以支持两种代理模式,是为了适配不同的场景(有无接口),下面我们分别拆解两种代理模式的核心原理。
二、核心原理
两种代理模式的核心区别,本质是「生成代理对象的方式不同」,底层依赖的技术也不同,我们分别拆解,结合简单代码示例,让你一眼看懂。
1. JDK 动态代理
JDK 动态代理是 JDK 自带的代理方式,不需要依赖任何第三方jar包,核心依赖 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口,其核心原理是:基于接口生成代理对象,代理对象会实现目标对象所实现的所有接口,通过调用接口方法,触发增强逻辑。
1.1 核心条件
JDK 动态代理有一个硬性要求:目标对象必须实现至少一个接口。如果目标对象没有实现任何接口,JDK 动态代理就无法生成代理对象,此时 Spring 会自动切换为 CGLIB 代理。
1.2 简单示例(手动实现 JDK 动态代理)
我们通过一个简单的案例,手动实现 JDK 动态代理,理解其底层逻辑:
go
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 定义一个接口(JDK动态代理必须有接口)
public interface UserService {
void addUser(String username);
}
// 2. 实现接口(目标对象)
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("核心业务:新增用户 -> " + username);
}
}
// 3. 实现 InvocationHandler(增强逻辑)
public class JdkProxyHandler implements InvocationHandler {
// 目标对象(被代理的对象)
private final Object target;
// 构造方法,注入目标对象
public JdkProxyHandler(Object target) {
this.target = target;
}
/**
* 代理对象的核心方法:所有代理对象的方法调用,都会触发这个方法
* @param proxy 代理对象本身
* @param method 目标方法(被调用的方法)
* @param args 目标方法的参数
* @return 目标方法的返回值
* @throws Throwable 异常抛出
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强逻辑:方法执行前
System.out.println("JDK代理 - 方法执行前:记录日志,用户新增操作开始");
// 执行目标对象的核心业务方法
Object result = method.invoke(target, args);
// 增强逻辑:方法执行后
System.out.println("JDK代理 - 方法执行后:记录日志,用户新增操作结束");
return result;
}
}
// 4. 测试 JDK 动态代理
public class JdkProxyTest {
public static void main(String[] args) {
// 目标对象
UserService target = new UserServiceImpl();
// 生成代理对象(核心:Proxy.newProxyInstance)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器(与目标对象一致)
target.getClass().getInterfaces(), // 目标对象实现的所有接口
new JdkProxyHandler(target) // 增强逻辑处理器
);
// 调用代理对象的方法(实际触发 invoke 方法)
proxy.addUser("张三");
}
}
1.3 运行结果
go
JDK代理 - 方法执行前:记录日志,用户新增操作开始
核心业务:新增用户 -> 张三
JDK代理 - 方法执行后:记录日志,用户新增操作结束
1.4 总结
JDK 动态代理的核心是「面向接口」,代理对象和目标对象是「兄弟关系」(都实现了同一个接口),通过接口方法间接调用目标对象的方法,增强逻辑在 InvocationHandler 的 invoke 方法中实现。
2. CGLIB 动态代理
CGLIB(Code Generation Library)是一个第三方代码生成库,Spring 已经内置了 CGLIB 依赖(无需额外导入),其核心原理是:基于继承生成代理对象,代理对象会继承目标对象,重写目标对象的方法,在重写的方法中插入增强逻辑。
2.1 核心条件
CGLIB 代理没有接口要求,目标对象可以有接口,也可以没有接口。它通过继承目标对象实现代理,因此有一个注意点:目标对象的方法不能被 final 修饰(如果方法被 final 修饰,子类无法重写,CGLIB 无法实现增强)。
2.2 简单示例(手动实现 CGLIB 代理)
同样通过案例,手动实现 CGLIB 代理,对比 JDK 代理的区别:
go
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 1. 目标对象(无需实现接口)
public class OrderService {
public void addOrder(String orderNo) {
System.out.println("核心业务:新增订单 -> " + orderNo);
}
// final 方法(CGLIB 无法增强,重写不了)
public final void deleteOrder(String orderNo) {
System.out.println("删除订单 -> " + orderNo);
}
}
// 2. 实现 MethodInterceptor(增强逻辑,类似 JDK 的 InvocationHandler)
public class CglibProxyInterceptor implements MethodInterceptor {
// 目标对象
private final Object target;
public CglibProxyInterceptor(Object target) {
this.target = target;
}
/**
* 代理对象的核心方法:所有代理对象的方法调用,都会触发这个方法
* @param o 代理对象本身
* @param method 目标方法
* @param args 目标方法参数
* @param methodProxy 方法代理(用于调用目标方法)
* @return 目标方法返回值
* @throws Throwable 异常抛出
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 增强逻辑:方法执行前
System.out.println("CGLIB代理 - 方法执行前:记录日志,订单新增操作开始");
// 执行目标对象的核心业务方法(两种方式,推荐用 methodProxy.invokeSuper)
Object result = methodProxy.invokeSuper(o, args);
// 不推荐:method.invoke(target, args)(会触发二次代理,效率低)
// 增强逻辑:方法执行后
System.out.println("CGLIB代理 - 方法执行后:记录日志,订单新增操作结束");
return result;
}
}
// 3. 测试 CGLIB 动态代理
public class CglibProxyTest {
public static void main(String[] args) {
// 目标对象
OrderService target = new OrderService();
// 生成代理对象(核心:Enhancer)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类(目标对象)
enhancer.setCallback(new CglibProxyInterceptor(target)); // 设置增强逻辑
OrderService proxy = (OrderService) enhancer.create(); // 生成代理对象
// 调用代理对象的方法(可增强)
proxy.addOrder("ORDER20260417001");
// 调用 final 方法(无法增强,直接执行目标方法)
proxy.deleteOrder("ORDER20260417001");
}
}
2.3 运行结果
go
CGLIB代理 - 方法执行前:记录日志,订单新增操作开始
核心业务:新增订单 -> ORDER20260417001
CGLIB代理 - 方法执行后:记录日志,订单新增操作结束
删除订单 -> ORDER20260417001
注意:deleteOrder 方法被 final 修饰,CGLIB 无法重写,因此没有执行增强逻辑,直接调用了目标对象的方法。
2.4 核心总结
CGLIB 代理的核心是「面向继承」,代理对象是目标对象的「子类」,通过重写目标方法实现增强,无需目标对象实现接口,但目标方法不能是 final 修饰。
三、区别对比
这是本文的核心内容,也是面试高频考点。我们从「底层原理、依赖条件、性能、适用场景」等8个维度,对比两种代理模式的区别,用表格呈现,清晰易懂,方便记忆。
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 底层原理 | 基于接口,通过 Proxy 类生成代理对象,实现目标接口 | 基于继承,通过 Enhancer 生成代理对象,继承目标对象 |
| 依赖条件 | 目标对象必须实现至少一个接口 | 无接口要求,目标对象可无接口 |
| 方法限制 | 只能增强接口中定义的方法,无法增强类中独有的方法 | 可增强目标对象的所有非 final 方法(final 方法无法重写,不能增强) |
| 代理对象关系 | 代理对象与目标对象是「兄弟关系」(同实现一个接口) | 代理对象与目标对象是「父子关系」(代理对象是目标对象的子类) |
| 性能表现 | 生成代理对象速度快,执行速度稍慢(需要通过反射调用接口方法) | 生成代理对象速度慢(需要生成子类字节码),执行速度快(直接调用子类方法,无需反射) |
| 依赖jar包 | JDK 自带,无需额外导入 | 需要 CGLIB 依赖(Spring 已内置,无需手动导入) |
| Spring 优先级 | 首选:如果目标对象有接口,默认使用 JDK 动态代理 | 兜底:如果目标对象无接口,自动切换为 CGLIB 代理;也可手动指定强制使用 CGLIB |
| 适用场景 | 目标对象有接口(如 Service 层接口、Dao 层接口),适合大多数企业级项目场景 | 目标对象无接口(如普通工具类)、需要增强类中独有的非 final 方法 |
补充说明:Spring Boot 2.x 版本后,默认情况下,如果目标对象有接口,使用 JDK 动态代理;如果目标对象无接口,使用 CGLIB 代理。如果想强制使用 CGLIB 代理,只需在配置文件中添加一行配置:spring.aop.proxy-target-class=true。
四、Spring AOP 代理选择源码解析
很多同学好奇:Spring 是如何自动选择 JDK 还是 CGLIB 代理的?我们通过 Spring 核心源码片段,简单解析其选择逻辑,不用深入看懂每一行代码,重点理解核心判断逻辑即可。
go
// Spring AOP 代理选择的核心类:DefaultAopProxyFactory
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 核心判断逻辑:
// 1. config.isOptimize():是否开启优化(默认false)
// 2. config.isProxyTargetClass():是否强制使用 CGLIB 代理(默认false)
// 3. hasNoUserSuppliedProxyInterfaces(config):目标对象是否没有实现任何接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
// 如果目标对象是接口,还是使用 JDK 动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则,使用 CGLIB 代理
return new ObjenesisCglibAopProxy(config);
} else {
// 目标对象有接口,使用 JDK 动态代理
return new JdkDynamicAopProxy(config);
}
}
// 判断目标对象是否没有实现任何接口(除了 Spring 内部的接口)
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] interfaces = config.getProxiedInterfaces();
return interfaces == null || interfaces.length == 0 ||
(interfaces.length == 1 && SpringProxy.class.isAssignableFrom(interfaces[0]));
}
}
核心逻辑总结
-
- 如果配置了
proxy-target-class=true(强制使用 CGLIB),无论目标对象是否有接口,都使用 CGLIB 代理(除非目标对象是接口);
- 如果配置了
-
- 如果没有强制配置,判断目标对象是否有接口:有接口 → JDK 代理,无接口 → CGLIB 代理;
-
- 即使目标对象有接口,如果开启了优化(isOptimize=true),也会使用 CGLIB 代理(优化的核心是提升执行速度)。
五、代理失效的3种高频场景及解决方案
在实际项目中,经常会遇到「AOP 增强不生效」的问题,本质都是「代理对象未被调用」,结合两种代理模式的特点,我们总结3种高频失效场景,给出解决方案,帮你快速排错。
场景1:目标方法被 final 修饰(CGLIB 代理失效)
❌ 问题现象:使用 CGLIB 代理时,final 修饰的方法,增强逻辑(如日志、限流)不生效;
✅ 原因:CGLIB 代理基于继承,final 方法无法被子类重写,因此无法插入增强逻辑;
✅ 解决方案:移除目标方法的 final 修饰符,确保方法可被重写。
场景2:目标对象自己调用自己的方法(代理失效)
❌ 问题现象:在 Service 类中,方法 A 调用方法 B,方法 B 添加了 AOP 增强注解(如 @DS、@Log),但增强逻辑不生效;
✅ 原因:方法 A 直接调用方法 B,是「目标对象内部调用」,没有经过代理对象,因此无法触发增强逻辑(无论是 JDK 还是 CGLIB 代理,都会失效);
✅ 解决方案:
-
- 方式1:通过 Spring 上下文获取代理对象,再调用方法 B(推荐);
-
- 方式2:在 Service 类中注入自身代理对象(用 @Autowired 注入,避免直接 this 调用);
-
- 方式3:使用 AopContext.currentProxy() 获取当前代理对象,再调用方法 B。
go
// 错误示例(内部调用,代理失效)
@Service
public class OrderService {
public void methodA() {
// this 是目标对象,不是代理对象,methodB 的增强不生效
this.methodB();
}
@Log // AOP 增强注解
public void methodB() {
System.out.println("核心业务方法");
}
}
// 正确示例(通过代理对象调用)
@Service
public class OrderService {
@Autowired
private OrderService orderService; // 注入自身代理对象
public void methodA() {
// 调用代理对象的 methodB,增强生效
orderService.methodB();
}
@Log
public void methodB() {
System.out.println("核心业务方法");
}
}
场景3:目标对象未被 Spring 管理(代理失效)
❌ 问题现象:手动 new 出来的目标对象,添加了 AOP 增强注解,但增强逻辑不生效;
✅ 原因:Spring AOP 只对 Spring 容器管理的 Bean 生成代理对象,手动 new 的对象不属于 Spring 管理,无法生成代理;
✅ 解决方案:通过 @Autowired、@Resource 等方式注入目标对象,不要手动 new。
七、文末小结
Spring AOP 的两种代理模式,是 AOP 核心底层知识,也是企业面试的高频考点,总结下来核心要点:
-
- JDK 动态代理:面向接口,无额外依赖,生成快、执行慢,适合有接口的场景;
-
- CGLIB 动态代理:面向继承,无接口要求,生成慢、执行快,适合无接口或需要增强类独有方法的场景;
-
- Spring 会自动选择代理模式,也可通过配置强制使用 CGLIB;
-
- 代理失效的核心原因是「未调用代理对象」,重点关注 final 方法、内部调用、Spring 管理这3个场景。
掌握这两种代理模式的区别和适用场景,不仅能帮你快速排查项目中的 AOP 问题,还能在面试中脱颖而出。其实不用死记硬背,理解底层原理(接口 vs 继承),很多区别自然就能记住。
如果你在项目中遇到代理相关的问题,或者有其他疑问,欢迎在评论区留言交流,一起避坑、一起进步!
别忘了点赞+在看+收藏三连,关注我,解锁更多 Spring AOP 实战干货,下期再见❤️