★ Spring AOP 的原理是什么?JDK 动态代理与 CGLIB 怎么选?
结论先行:Spring AOP 的本质是“代理 + 拦截器链”:切面逻辑被织入代理对象,外部调用先经过代理,再按顺序执行通知。实现上,对接口使用 JDK 动态代理(Proxy + InvocationHandler),对普通类使用 CGLIB(生成目标类子类)。Spring Boot 2.x 起默认开启 proxyTargetClass=true,无接口的类也能被代理。
两种代理对比表
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 原理 | 为接口生成代理类 | 生成目标类的子类并重写方法 |
| 前提 | 目标必须有接口 | 无需接口 |
| 限制 | 只能代理接口声明的方法 | final 类/final 方法无法代理 |
| 性能 | 创建快、调用走反射 | 创建慢、调用走字节码较快 |
| 默认策略 | 老版本接口优先 | Spring Boot 2.x+ 默认强制开启 |
通知类型与顺序
- @Before 前置、@After 后置、@AfterReturning 返回后、@AfterThrowing 异常后、@Around 环绕;
- 记忆顺序:@Around 前段最先 → @Before → 目标方法 → @AfterReturning/@AfterThrowing(二选一)→ @After → @Around 后段收尾;多个切面可用 @Order 控制先后。
环绕通知示例
@Aspect
@Component
public class TimeAspect {
@Around("@within(org.springframework.stereotype.Service)")
public Object timing(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed(); // 放行到目标方法
} finally {
System.out.println("cost = " + (System.nanoTime() - start) + " ns");
}
}
}
为什么“自调用”不生效
@Service
public class OrderService {
public void create() { this.updateStock(); } // this 指向原始对象,代理不介入
@Transactional
public void updateStock() { /* 扣库存逻辑 */ }
}
外部调用 create() 时,只有 create 经过代理;内部 this.updateStock() 直接命中原始 Bean,@Transactional/@Async 全部失效。解法:注入自身代理、拆到另一个 Bean、或用 AopContext.currentProxy()。
常见追问 / 记忆点
- 追问:怎么强制 JDK 代理或 CGLIB?答:配置 spring.aop.proxy-target-class=false/true,或设置 @EnableAspectJAutoProxy 的 proxyTargetClass。
- 追问:目标类既没接口又不是 final,默认能代理吗?答:能,Spring Boot 默认走 CGLIB 子类代理。
- 记忆点:代理是事务/缓存/异步的地基;“自调用不走代理”是注解失效的万能背锅点。