★ 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 子类代理。
  • 记忆点:代理是事务/缓存/异步的地基;“自调用不走代理”是注解失效的万能背锅点。
笔记加载中…