Spring AOP 中的动态代理主要有两种方式,JDK 动态代理 和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。

如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

两种代理方式是 AOP 的物理基础,但”原理”还应包含 Spring 层的三块机制:代理决策与创建时机(AnnotationAwareAspectJAutoProxyCreator)、通知的链式执行(拦截器链)、以及 Advisor 的匹配模型。本文把从 @Aspect 到代理对象调用的完整链路讲通。

创建时机:后处理器决策

容器在 Bean 生命周期的 postProcessAfterInitialization 阶段,由 AnnotationAwareAspectJAutoProxyCreator(一个 BeanPostProcessor)判断是否需要代理:

  1. 收集所有切面(@Aspect bean)并解析为 Advisor(pointcut + advice 的组合体)
  2. 用每个 Advisor 的 pointcut 匹配当前 bean 的类与方法(canApply)
  3. 任一匹配成功 → 创建代理;否则返回原对象

所以代理是按 bean 逐个决策的:没有匹配切面的 bean 不会被代理,开销只发生在需要的地方。

代理方式决策

  • 目标类实现接口且未强制 CGLIB → JDK 动态代理(代理接口方法)
  • 无接口、或 proxyTargetClass=true(Spring Boot 默认)→ CGLIB(生成子类)
  • CGLIB 限制:final 类/方法不可代理;构造器会被调用(Objenesis 绕过参数构造)

Boot 默认 CGLIB 的动机:避免”按类注入失败”(JDK 代理对象不是目标类实例),统一代理模型减少困惑。

调用时:拦截器链与责任链执行

代理对象被调用时,并非直接反射调切面,而是:

  1. 获取该方法的拦截器链(MethodInterceptor 列表):把匹配的所有 Advisor 按顺序适配为拦截器(@Around/@Before/@After 等被适配为不同 Interceptor)
  2. 通过 ReflectiveMethodInvocation.proceed() 递归执行链:每个拦截器决定”调用 chain.proceed() 的时机”——Before 在 proceed 前做事、After 在 proceed 后(finally 语义)、Around 包裹 proceed、AfterThrowing 在 catch 中
  3. 链尾是目标方法反射调用
// 拦截器伪码:Before 语义
public Object invoke(MethodInvocation inv) {
advice.before(inv.getMethod(), inv.getArguments(), inv.getThis());
return inv.proceed(); // 交给链上下一个
}

通知顺序(多个切面)由 @Order/Ordered 决定:同优先级时进入顺序与退出顺序相反(洋葱模型)。这解释了”Before 们按 order 正序、After 们逆序”的现象。

Advisor 匹配模型:pointcut 的两级筛选

  • ClassFilter:类级快速排除(within/bean 表达式在此生效)
  • MethodMatcher:方法级精确匹配(execution/@annotation);带参数的匹配器还可看运行时参数

两级筛选让”包名不匹配的 bean”在类级就被跳过,避免每方法都做表达式求值——匹配性能是 AOP 启动后零开销的保证(决策只在 bean 创建时做一次,结果缓存)。

与 AspectJ 的关系

Spring AOP 借用了 AspectJ 的表达式语法与注解模型(@Aspect/@Pointcut),但织入方式是自己的运行期代理(Spring AOP),而非 AspectJ 编译期/加载期织入。差异边界:

  • Spring AOP:只支持方法执行连接点、只能代理容器 bean、运行期织入、无额外构建步骤
  • AspectJ:字段/构造器/调用点皆可增强、可代理任意对象、编译期织入零运行开销、需 ajc 或 agent

需要增强非 Spring 管理对象或字段访问时,才需要真 AspectJ。

性能视角

  • 代理创建:一次性成本(bean 初始化时),CGLIB 生成子类字节码较慢但只发生一次
  • 代理调用:拦截器链 + 反射调用目标;JDK8+ 反射已优化,链长度(切面数)才是主要变量
  • 无匹配切面的 bean 无代理零开销

原理主线:后处理器按 Advisor 匹配决策代理 → JDK/CGLIB 生成代理对象 → 调用时拦截器链责任链执行通知 → 链尾反射调目标。抓住”决策一次、执行成链”两个特征,Spring AOP 从配置到运行的黑盒就完全透明了。