Spring AOP 核心原理:JDK 与 CGLIB 动态代理实战解析(2026 最新版)
Spring AOP(Aspect-Oriented Programming)是 Spring 最强大的特性之一,其核心实现完全依赖动态代理。Spring 不会修改你的源代码,而是在运行时生成代理对象,在目标方法调用前后织入增强逻辑(Advice)。
Spring AOP 底层只支持两种动态代理:
- JDK 动态代理(官方推荐接口优先)
- CGLIB 动态代理(Spring Boot 默认)
下面从原理、源码、实战代码、选择逻辑、性能、陷阱全方位说透。
1. 为什么 Spring AOP 需要动态代理?
AOP 要实现无侵入横切关注点(如事务、日志、安全、缓存),必须在不改动业务代码的前提下拦截方法调用。
代理模式(Proxy Pattern)是最佳方案:
- 代理对象持有目标对象引用
- 调用代理 → 代理先执行增强 → 再调用目标方法
Spring 在 Bean 初始化后,通过 AbstractAutoProxyCreator(BeanPostProcessor)为符合切点的 Bean 创建代理。
2. JDK 动态代理详解(Interface-based)
原理:
- JDK 内置
java.lang.reflect.Proxy - 只能代理实现了接口的类
- 运行时生成一个实现目标接口的代理类(
$Proxy0、$Proxy1…) - 所有方法调用都通过
InvocationHandler.invoke()转发
手写 JDK 动态代理完整示例:
// 接口
public interface UserService {
void addUser(String name);
String getUserName(int id);
}
// 目标类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public String getUserName(int id) {
return "用户" + id;
}
}
// InvocationHandler(增强逻辑在这里)
public class LogInvocationHandler implements InvocationHandler {
private final Object target; // 目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【JDK代理】方法开始执行: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args); // 调用目标方法
long end = System.currentTimeMillis();
System.out.println("【JDK代理】方法执行结束: " + method.getName() + ", 耗时: " + (end - start) + "ms");
return result;
}
}
// 测试
public class JdkProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 代理的接口
new LogInvocationHandler(target)
);
proxy.addUser("张三"); // 会触发 invoke
System.out.println(proxy.getUserName(100));
}
}
特点:
- 生成的代理类实现目标接口
- 只能增强接口中声明的方法
- 性能较高(反射调用,但 JDK 8+ 优化明显)
3. CGLIB 动态代理详解(Class-based)
原理:
- CGLIB(Code Generation Library)通过字节码生成技术(ASM)在运行时生成目标类的子类
- 代理类是目标类的子类,重写需要增强的方法
- 通过
MethodInterceptor拦截方法调用 - Spring 已将 CGLIB repackage 到
spring-core中,无需额外依赖
手写 CGLIB 动态代理示例:
// 目标类(可以没有接口)
public class OrderService {
public void createOrder(String orderNo) {
System.out.println("创建订单: " + orderNo);
}
public final void finalMethod() { // final 方法无法被代理
System.out.println("这是 final 方法");
}
}
// MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB代理】方法开始: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法(目标方法)
System.out.println("【CGLIB代理】方法结束: " + method.getName());
return result;
}
}
// 测试
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class); // 设置父类
enhancer.setCallback(new LogMethodInterceptor()); // 设置回调
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("ORD-001");
proxy.finalMethod(); // final 方法不会被增强
}
}
特点:
- 可以代理没有实现接口的类
- 不能代理
final类和final方法(无法继承/重写) - 生成的代理类是目标类的子类(
OrderService$$EnhancerBySpringCGLIB...)
4. Spring AOP 如何选择代理方式(2026 最新机制)
Spring Framework 官方规则(AopProxyFactory):
- 目标 Bean 实现了至少一个接口 → 默认使用 JDK 动态代理
- 目标 Bean 没有实现接口 → 使用 CGLIB
Spring Boot 默认行为(从 2.0 开始):
- 默认开启
spring.aop.proxy-target-class=true - 统一使用 CGLIB(即使有接口也用 CGLIB)
- 原因:行为更一致,避免接口注入时的类型转换问题(
ClassCastException),对@Configuration类等必须 subclassing
配置方式(application.yml 或 @EnableAspectJAutoProxy):
spring:
aop:
proxy-target-class: true # 默认 true(CGLIB)
# proxy-target-class: false # 强制 JDK(需有接口)
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制 CGLIB
Spring 内部代理创建流程:
AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor)AbstractAutoProxyCreator.createProxy()DefaultAopProxyFactory判断创建JdkDynamicAopProxy还是CglibAopProxy- 生成代理对象,替换原始 Bean
5. 实战:在 Spring Boot 中验证代理类型
@Component
public class ProxyTypeChecker implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext ctx) {
Object bean = ctx.getBean("userServiceImpl"); // 你的 Bean 名
System.out.println("Bean 类型: " + bean.getClass().getName());
System.out.println("是否 JDK 代理: " + Proxy.isProxyClass(bean.getClass()));
System.out.println("是否 CGLIB 代理: " + bean.getClass().getName().contains("$$EnhancerBySpringCGLIB"));
}
}
查看生成的代理类(调试技巧):
- 添加 VM 参数:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true(JDK) - 或使用 Arthas / JClassLib 查看
$$EnhancerBySpringCGLIB类
6. 性能、优缺点与适用场景
| 维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 代理依据 | 接口 | 类(生成子类) |
| 性能 | 较好(反射优化后) | 稍差(首次生成字节码有开销) |
| 内存占用 | 较低 | 较高(子类 + FastClass) |
| 限制 | 只能代理接口方法 | 不能代理 final 类/方法 |
| Spring Boot 默认 | 需手动设置 false | 默认(推荐) |
| 适用场景 | 接口驱动开发、微服务 | 遗留代码、无接口类、@Configuration |
结论:现代项目(Spring Boot 3/4)强烈推荐保持默认 CGLIB,除非有明确性能瓶颈或必须用 JDK 的场景。
7. 常见陷阱与最佳实践
- 自调用失效(最经典问题)
this.method()不会走代理 → 增强逻辑不生效。
解决:注入自己(@Autowired UserService self)或使用AopContext.currentProxy()(需开启 expose-proxy=true)。 - final 方法/类无法增强 → CGLIB 直接失效。
- private 方法无法被 AOP(无论哪种代理)。
- 事务嵌套问题 → 常因代理类型导致传播行为异常。
最佳实践:
- 优先定义接口(便于测试 + JDK 代理)
- 保持 Spring Boot 默认 CGLIB 配置
- 复杂场景用
ProxyFactory手动创建代理 - 性能敏感场景可考虑 AspectJ(编译期织入,非代理)
掌握了 JDK 和 CGLIB 动态代理,就真正理解了 Spring AOP、“@Transactional 为什么能生效”、“为什么 this 调用不走事务”等所有“魔法”的底层。
想继续深入以下任意部分,随时告诉我:
- Spring AOP 完整源码流程(ProxyFactory → AdvisedSupport)
- @Aspect 注解如何解析为 Advisor
- AspectJ vs Spring AOP 对比
- 自定义 AnnotationAwareAspectJAutoProxyCreator
- 性能压测对比代码
我可以继续给你更深层次的源码级解析!