Skip to content

能说说 Spring 拦截链的实现吗? ​

  • 标签:后端、Spring

🏛 业务架构师 ​

Spring 拦截链(责任链)的实现我概括为三层结构,这也是我向团队讲 AOP 时的标准框架:

1. 切面组织层:@Aspect 里定义切点(Pointcut)和通知(Advice),AnnotationAwareAspectJAutoProxyCreator 这个 BeanPostProcessor 在 Bean 初始化后,找出匹配的切面。

2. 代理创建层:为符合条件的 Bean 生成代理(JDK 或 CGLIB),把所有匹配的通知按序组装成一个 MethodInterceptor 链。顺序规则:@Order 控制 Aspect 间顺序,同一个切面内按"环绕 → 前置 → 业务 → 返回/异常 → 后置 → 环绕退出"排布。

3. 运行调度层:调用代理方法时进入 CglibAopProxy.DynamicAdvisedInterceptor(或 JdkDynamicAopProxy),构造 MethodInvocation(CglibMethodInvocation),proceed() 递归推进链条——每个拦截器调用 invoke,内部再调 proceed() 触发下一个,直到真正的目标方法,异常沿栈回溯触发对应的 AfterThrowing。

业务上这个模式的复用价值很高:我自己在营销规则引擎、风控审批流里都手工实现过类似责任链(Filter/Interceptor 数组 + chain.proceed()),Gateway 的过滤器链、MVC 的 HandlerInterceptor、Dubbo 的 Filter 全是同一套心智模型。

理解它的实际回报:排查"事务为什么没生效""切面为什么执行了两遍"这类线上问题时有章可循——沿着代理对象 → 拦截链 → 目标方法这条线走查即可。

🏗 系统架构师 ​

实现:责任链模式加递归推进,核心类是 ReflectiveMethodInvocation。

  • 框架把匹配的通知(Advisor)转成 MethodInterceptor,按 @Order 排序成链;
  • proceed() 递归推进:每个拦截器执行前置逻辑,调用 mi.proceed() 放行下一环,返回后执行后置逻辑;@Around 最外层,@Before 在前,@After 系列依序包裹;
  • 链尾反射调用真实方法。

为什么架构师要懂这条链:

  1. 顺序即正确性:事务切面与缓存切面的执行顺序、@Async 与事务的叠加顺序直接决定语义(先提交后发消息,还是反过来),我全部用 @Order 显式声明,不赌默认顺序;
  2. 排障能力:线上栈里十几层 invoke 就是这条链,能读懂它才能定位「增强为什么没生效」;
  3. 性能认知:每层只是方法调用加递归栈,开销纳秒级,真正的成本在切面里干的事(比如远程鉴权),而不是链本身——优化要对准真瓶颈。

💻 全栈开发专家 ​

Spring AOP 拦截链本质是责任链 + 递归回调。入口在 JdkDynamicAopProxy#invoke 或 CGLIB 的拦截回调:调用发生时,把匹配该方法的所有 Advisor 转成 MethodInterceptor 列表,包进 ReflectiveMethodInvocation,靠 proceed() 驱动:

java
// ReflectiveMethodInvocation 核心逻辑(简化)
public Object proceed() throws Throwable {
    if (this.currentInterceptorIndex == this.interceptors.size() - 1) {
        return invokeOriginalMethod();        // 链走完,执行真实方法
    }
    MethodInterceptor next = this.interceptors.get(++this.currentInterceptorIndex);
    return next.invoke(this);                 // 每个拦截器自己决定是否放行
}

每个拦截器拿到 invocation,我们在 @Around 里手写的 pjp.proceed() 就是把链往下推;不调就短路——限流直接抛异常就是这个原理。多个切面的顺序由 @Order 控制,值小的在外层,画成洋葱圈模型就不会记错。

调试技巧:想看一个方法被套了几层,在 ReflectiveMethodInvocation#proceed 打断点展开 interceptors 列表——事务、异步、自定义日志每个都是一个 MethodInterceptor。理解这个模型后,@Transactional 和 @Async 叠加时"谁包谁、线程在哪一层切换"都能自己推演出来,不用背结论。