Spring AOP 原理:JDK 动态代理 vs CGLIB
提出问题
AOP 是 Spring 最核心的能力之一,但很多开发者只停留在「@Aspect 加 @Around 就能切面」的会用层面,问到底层实现就卡住了。面试官问这道题,核心考察三点:两种代理方式的实现原理、Spring 什么时候自动切换、以及自调用为什么会失效。这三个问题从源码级理解,才算真正搞懂了 Spring AOP。
分析问题
两种代理方式
Spring AOP 基于代理模式实现,底层有两种代理方式:JDK 动态代理和 CGLIB。
JDK 动态代理:基于 java.lang.reflect.Proxy 和 InvocationHandler,运行时生成一个实现了目标类所有接口的代理类。代理类与目标类实现相同的接口,但不继承目标类。因此,JDK 代理只能代理接口中声明的方法,不能代理目标类自己定义的、不在接口中的方法。
// 目标类实现了接口
public interface UserService {
void createUser(String name);
}
public class UserServiceImpl implements UserService {
@Override
public void createUser(String name) {
System.out.println("创建用户: " + name);
}
// 非接口方法,JDK 代理无法拦截
public void internalMethod() {
System.out.println("内部方法");
}
}
// JDK 动态代理实现
public class JdkProxyFactory {
public static UserService createProxy(UserService target) {
return (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("[AOP前置] 方法: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("[AOP后置] 方法: " + method.getName());
return result;
}
);
}
}
// 使用
UserService proxy = JdkProxyFactory.createProxy(new UserServiceImpl());
proxy.createUser("张三"); // AOP生效
// proxy.internalMethod(); // 编译都不通过,因为 UserService 接口没这个方法CGLIB 代理:基于 ASM 字节码技术,运行时生成目标类的子类,通过方法拦截(MethodInterceptor)实现增强。因为生成的是子类,所以可以代理所有非 final 的方法,包括目标类自己定义的、不在接口中的方法。但 final 方法和 final 类无法被代理(子类无法重写 final 方法,无法继承 final 类)。
// 目标类没有实现接口
public class ReportService {
public void generateReport() {
System.out.println("生成报告");
}
// final 方法,CGLIB 不能代理
public final void logSteps() {
System.out.println("记录日志");
}
}
// CGLIB 代理实现(需要 cglib 依赖)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
public class CglibProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("[AOP前置] 方法: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("[AOP后置] 方法: " + method.getName());
return result;
});
return (T) enhancer.create();
}
}
// 使用
ReportService proxy = CglibProxyFactory.createProxy(ReportService.class);
proxy.generateReport(); // AOP生效
proxy.logSteps(); // 执行了,但AOP不生效(final方法无法拦截)Spring 自动切换策略
Spring 通过 DefaultAopProxyFactory 决定使用哪种代理方式,核心逻辑极其简单:
// DefaultAopProxyFactory.createAopProxy()
public AopProxy createAopProxy(AdvisedSupport config) {
// 条件1: 配置了 optimize=true 或 proxyTargetClass=true 或 没有接口
if (config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// 条件2: 目标类是接口 或 是 JDK 代理类,走 JDK 代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
} else {
// 有接口且没设置 proxyTargetClass,走 JDK 代理
return new JdkDynamicAopProxy(config);
}
}从 Spring Boot 2.x 开始,默认策略改为 优先使用 CGLIB,即使目标类实现了接口。这是因为 spring.aop.proxy-target-class=true 在 Spring Boot 中默认开启。可以通过 @EnableAspectJAutoProxy(proxyTargetClass = false) 强制切换回 JDK 代理。
自调用问题
这是 AOP 面试中最经典也最容易答不完整的坑。自调用指同一个类中,方法 A 调用方法 B,如果 B 有 @Transactional 或 @Async 等注解,增强不会生效。
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
saveOrder(order);
// 调用本类的 sendNotification——这里走的是 this,不是代理对象
this.sendNotification(order);
}
@Async
public void sendNotification(Order order) {
// 异步不会生效!因为 this 是原始对象,不是代理对象
emailService.send(order);
}
}原因:Spring 容器中的是代理对象,但 this.sendNotification() 调用的是目标对象(原始 bean)的方法,根本没有经过代理调用链。AOP 增强失效。
解法有两种:
// 解法1:注入自身代理(注意循环依赖风险)
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入的是代理对象
@Transactional
public void createOrder(Order order) {
saveOrder(order);
self.sendNotification(order); // 通过代理对象调用
}
@Async
public void sendNotification(Order order) {
emailService.send(order);
}
}
// 解法2:AopContext.currentProxy()(需要 exposeProxy=true)
@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class OrderService {
@Transactional
public void createOrder(Order order) {
saveOrder(order);
((OrderService) AopContext.currentProxy()).sendNotification(order);
}
}Spring 6 的变化
Spring 6 中 CGLIB 被替换为 ByteBuddy(ObjenesisCglibAopProxy 改为 ObjenesisByteBuddyAopProxy),Spring Boot 3 不再直接依赖 CGLIB。但对外 API 完全兼容,开发者不需要改代码。ByteBuddy 的优势在于:更现代的字节码操作 API、更好的性能、更少的依赖冲突。
总结
| 对比维度 | JDK 动态代理 | CGLIB / ByteBuddy |
|---|---|---|
| 原理 | 生成接口实现类 | 生成目标类子类 |
| 依赖要求 | 必须实现至少一个接口 | 不需要接口 |
| 可代理范围 | 仅接口方法 | 非 final 方法 |
| 性能 | 创建快,调用慢 | 创建慢,调用快 |
| Spring Boot 默认 | 否(除非显式关闭 proxyTargetClass) | 是 |
| 自调用 | 失效 | 失效(同) |
生产避坑:自调用是 AOP 失效的第一大原因,排查时先看调用链走了 this 还是代理对象。另一个常见坑是 @Async 和 @Transactional 混用——两者都依赖 AOP 代理,如果同时在同一个类中,@Transactional 的代理在 @Async 的代理之内,注意两层代理的执行顺序。Spring 6 之后 ByteBuddy 替代 CGLIB,但两者行为完全一致,不需要迁移存量代码。
参考:Spring 源码 — DefaultAopProxyFactory、JdkDynamicAopProxy、CglibAopProxy;Spring Boot 自动配置 — AopAutoConfiguration;Spring 6 迁移指南 — CGLIB replaced by ByteBuddy