专栏文章
专栏文章
Spring Framework 系列
1. Spring Framework 系列 #01:Spring 容器启动流程源码分析 2. Spring Framework 系列 #02:Spring 三级缓存与循环依赖 3. Spring Framework 系列 #03:Spring 事务管理 4. Spring Framework 系列 #04:Spring IoC 与 Bean 生命周期 5. Spring Framework 系列 #05:Spring AOP

Spring Framework 系列 #05:Spring AOP

发布于 2026-05-26 09:43 👁 8 次阅读
#源码解析#java#spring#aop

本文从 Kiczales 1997 年提出 AOP 范式出发,阐明"横切关注点"问题的本质,逐层深入 Spring AOP 的代理机制、Advice 执行链路源码,并厘清 Spring AOP 与 AspectJ 的边界。


目录

章节 说明
理论基础 AOP 编程范式起源、横切关注点、核心术语
Spring AOP vs AspectJ 两者定位、织入时机、能力边界
实现原理 JDK 动态代理 vs CGLIB,选择策略
源码链路 代理创建 → Advice 责任链执行完整路径
通知类型与执行顺序 5 种通知、单切面/多切面执行顺序
切入点表达式 execution / @annotation / within 等
常见坑 失效场景与根因

理论基础

横切关注点问题

1997 年,Gregor Kiczales 等人在施乐 PARC 研究中心提出 AOP(发表于 ECOOP '97),动机是解决 OOP 的一个固有局限:横切关注点(crosscutting concerns)无法被 OOP 干净地模块化

典型的横切关注点:

日志记录    安全鉴权    事务管理    缓存    监控/埋点    异常处理

用 OOP 实现时,这些关注点会导致两种问题:

问题 说明
代码散射(Scattering) 同一段日志/安全逻辑分散在数百个类中,改动一处需改动所有地方
代码纠缠(Tangling) 业务逻辑与日志/事务代码混在一个方法里,职责不清晰

AOP 的解法:将横切关注点抽取为独立的切面(Aspect),通过织入(Weaving) 机制在不修改业务代码的前提下将其应用到目标位置。

"AOP complements OOP by providing another way of thinking about program structure." — Spring Framework 官方文档

核心术语

术语 英文 说明
切面 Aspect 横切关注点的模块化封装,包含 Pointcut + Advice
连接点 Join Point 程序执行中可被拦截的点(Spring AOP 仅支持方法执行这一种)
切入点 Pointcut 连接点的选择谓词("哪些方法"),用表达式描述
通知 Advice 在切入点处执行的动作("做什么"),分 Before/After/Around 等
目标对象 Target 被代理的原始对象
代理 Proxy 包裹目标对象的增强对象,AOP 的实际载体
织入 Weaving 将切面应用到目标的过程
引介 Introduction 为现有类动态添加新接口/方法(Spring AOP 支持,较少用)

织入时机的三种选择

编译时织入(Compile-time weaving)
  → AspectJ 编译器(ajc)在编译阶段修改字节码
  → 性能最好,但需特殊编译流程

加载时织入(Load-time weaving, LTW)
  → JVM 加载类时通过 Java Agent 修改字节码
  → AspectJ 支持(-javaagent:aspectjweaver.jar)

运行时织入(Runtime weaving)
  → 通过动态代理在运行时创建增强对象 ← Spring AOP 采用此方式
  → 无需修改字节码,纯 Java 实现,灵活但有局限

Spring AOP vs AspectJ

维度 Spring AOP AspectJ
织入方式 运行时动态代理 编译时 / 加载时 / 运行时
Join Point 仅方法执行 方法调用、字段读写、构造器、静态初始化等
依赖 纯 Spring,无需额外工具 需要 AspectJ 编译器(ajc)或 Weaving Agent
性能 每次调用经过代理链,略有开销 字节码织入,接近原生性能
自调用 ⚠️ 无法拦截(同类内部调用绕过代理) ✅ 可以拦截
final 类/方法 ⚠️ CGLIB 无法代理 ✅ 可以织入
适用场景 企业应用中 80% 的 AOP 需求 需要字段级拦截或高性能的场景

Spring 的选择:Spring AOP 使用 AspectJ 的注解语法@Aspect@Pointcut@Before 等),但底层是自己的动态代理实现,而非 AspectJ 织入器。两者共用语法,实现独立。


实现原理

JDK 动态代理

// JdkDynamicAopProxy 实现 InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 获取该方法对应的拦截器链
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    if (chain.isEmpty()) {
        // 无拦截器,直接调用目标方法
        return AopUtils.invokeJoinpointUsingReflection(target, method, args);
    }
    // 有拦截器,构建责任链
    MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    return invocation.proceed();
}

条件:目标类实现了至少一个接口。代理类实现相同接口,Proxy.newProxyInstance() 生成。

CGLIB 代理

CGLIB 通过 ASM 操作字节码,生成目标类的子类,覆盖父类方法并在其中嵌入拦截逻辑。

限制

选择策略

// DefaultAopProxyFactory#createAopProxy
public AopProxy createAopProxy(AdvisedSupport config) {
    if (config.isOptimize() || config.isProxyTargetClass()
            || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config); // 目标本身是接口/代理类 → JDK
        }
        return new ObjenesisCglibAopProxy(config); // 否则 → CGLIB
    }
    return new JdkDynamicAopProxy(config); // 有接口且不强制 CGLIB → JDK
}

Spring Boot 2.x 起默认 spring.aop.proxy-target-class=true,强制 CGLIB。


源码链路

代理创建:BeanPostProcessor 阶段

Bean 初始化完成
  → AbstractAutoProxyCreator#postProcessAfterInitialization()
  → wrapIfNecessary(bean, beanName, cacheKey)
  → getAdvicesAndAdvisorsForBean()   // 找所有匹配该 Bean 的 Advisor
  → createProxy(beanClass, beanName, advisors, targetSource)
  → ProxyFactory#getProxy(classLoader)
  → DefaultAopProxyFactory#createAopProxy()  // 决定 JDK / CGLIB
  → 返回代理对象,替换容器中的原始 Bean

Advisor 匹配过程

// AbstractAdvisorAutoProxyCreator#findEligibleAdvisors(精简)
// 1. 从容器获取所有 Advisor Bean
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 2. 过滤出能匹配当前 Bean 的 Advisor
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
// 3. 排序(@Order / PriorityOrdered)
sortAdvisors(eligibleAdvisors);

Advice 执行:责任链模式

代理方法调用
  → ReflectiveMethodInvocation#proceed()
  → 遍历 interceptorList(MethodInterceptor 列表)
     ├── ExposeInvocationInterceptor(暴露 invocation 到 ThreadLocal)
     ├── AspectJAroundAdvice(Around → 调用 proceed() 推进链)
     ├── MethodBeforeAdviceInterceptor(Before)
     ├── AspectJAfterAdvice(After,finally 块执行)
     ├── AfterReturningAdviceInterceptor(AfterReturning)
     └── AspectJAfterThrowingAdvice(AfterThrowing)
  → 所有 interceptor 走完后调用目标方法

责任链用递归索引推进(currentInterceptorIndex++),Around 通知通过调用 joinPoint.proceed() 驱动链条继续。


通知类型与执行顺序

5 种通知

注解 执行时机 能获取返回值 能修改返回值 能捕获异常
@Before 目标方法执行前
@AfterReturning 正常返回后 ❌(只能读)
@AfterThrowing 抛出异常后 ✅(只能读)
@After 方法退出后(finally)
@Around 完全包裹目标方法

@Around 最强大,可以修改参数、返回值,吞掉或替换异常。@Before / @After 只能"观察",无法改变结果。

单切面内执行顺序(Spring 5.2.7+ 修正版)

正常执行:
  @Around(前半)→ @Before → 目标方法 → @Around(后半)→ @AfterReturning → @After

异常执行:
  @Around(前半)→ @Before → 目标方法(抛异常)→ @AfterThrowing → @After

⚠️ Spring 5.2.7 之前,同一切面内 @AfterReturning@After 的顺序与上述相反(@After 先于 @AfterReturning),5.2.7 修正为更符合直觉的顺序。

多切面执行顺序

多个切面作用于同一方法时,按 @Order 值排序,值越小优先级越高:

@Order(1) 切面 Around 前
  @Order(2) 切面 Around 前
    目标方法
  @Order(2) 切面 Around 后 / AfterReturning / After
@Order(1) 切面 Around 后 / AfterReturning / After

外层切面先进后出(类似 Filter 链)。不指定 @Order 时顺序不确定,多切面务必显式指定。


切入点表达式

execution(最常用)

execution([访问修饰符] 返回类型 [类路径.]方法名(参数类型) [throws 异常])

// 示例
execution(* com.example.service.*.*(..))          // service 包下所有方法
execution(public * com.example..*.save*(..))       // 所有 save 开头的 public 方法
execution(* *(..) throws IOException)              // 声明抛出 IOException 的方法

通配符:

其他常用表达式

表达式 说明 示例
@annotation(ann) 方法上有指定注解 @annotation(com.example.Log)
@within(ann) 类上有指定注解(该类所有方法) @within(org.springframework.stereotype.Service)
within(type) 指定类型内的方法 within(com.example.service.*)
bean(name) 指定 Spring Bean 名称 bean(orderService)
args(types) 参数类型匹配 args(java.io.Serializable)
@args(ann) 参数有指定注解 @args(com.example.Valid)

切入点复合

@Aspect
@Component
public class LogAspect {

    // 定义可复用的切入点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    @Pointcut("@annotation(com.example.annotation.Log)")
    public void logAnnotated() {}

    // 复合:service 层 且 有 @Log 注解
    @Around("serviceLayer() && logAnnotated()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        // ...
        return pjp.proceed();
    }
}

常见坑

# 现象 根因 解决方案
1 同类内部方法调用切面失效 this 调用绕过代理对象 注入自身 Bean 调用,或改用 AspectJ LTW
2 private / final 方法切面失效 CGLIB 无法覆盖 private/final 方法 改为 public,或用 AspectJ
3 接口上的注解切面失效(CGLIB 模式) CGLIB 代理的是类,读不到接口注解 注解加在实现类/方法上,或切换 JDK 代理
4 多切面顺序混乱 未指定 @Order 所有切面显式加 @Order(n)
5 @Around 忘记调用 proceed() 目标方法不会执行 必须调用 pjp.proceed() 并返回结果
6 @AfterReturning 拿不到返回值 未绑定 returning 参数 @AfterReturning(returning = "result")
7 @Transactional + 自定义切面顺序错误 事务切面和自定义切面 @Order 冲突 事务切面默认 Integer.MAX_VALUE,自定义切面设更大值则在事务外

坑 1 详解:自调用失效

@Service
public class OrderService {

    // ❌ this.audit() 直接调用,绕过代理,@Log 切面不触发
    public void placeOrder() {
        this.audit("place");
    }

    @Log
    public void audit(String action) { ... }
}

// ✅ 注入自身
@Service
public class OrderService {

    @Autowired
    private OrderService self;

    public void placeOrder() {
        self.audit("place"); // 通过代理调用,切面生效
    }

    @Log
    public void audit(String action) { ... }
}

坑 5 详解:@Around 必须 proceed

// ❌ 忘记 proceed,目标方法永远不会执行
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) {
    log.info("before");
    // 漏了 pjp.proceed()!
    return null;
}

// ✅ 正确写法
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    log.info("before: {}", pjp.getSignature());
    Object result = pjp.proceed(); // 推进责任链/执行目标方法
    log.info("after: {}", result);
    return result; // 必须返回,否则调用方拿到 null
}

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论