一个代理对象被调用时,方法调用的链路是怎样的?
当一个被 AOP 增强的代理对象的方法被调用时,其链路遵循一个经典的责任链模式 (Chain of Responsibility Pattern),通常被描述为“洋葱模型”或“拦截器链 (Interceptor Chain)”。
核心脉络:一个递归的调用链
整个过程不是简单的 代理 -> 目标
,而是一个 代理 -> 通知1 -> 通知2 -> ... -> 目标
的链式调用。
关键角色:
- 代理对象 (Proxy Object): 外部调用的入口。
- 拦截器/通知 (Interceptors/Advices): 每一个
@Before
,@Around
等通知都会被包装成一个统一的MethodInterceptor
对象。 ReflectiveMethodInvocation
: 这是整个调用链的“发动机”和“上下文管理器”。它封装了目标对象、目标方法、参数以及一个即将执行的拦截器列表。它的核心方法是proceed()
。
详细的方法调用链路步骤
假设我们有一个 UserService
的代理对象,它被两个切面(一个日志切面,一个安全切面)增强了。
调用链路如下:
1. 调用命中代理对象
外部代码(如 Controller)调用 userService.doSomething()
。这个调用首先命中代理对象。
2. 代理激活,准备执行链
- JDK 代理:
JdkDynamicAopProxy
的invoke()
方法被触发。 - CGLIB 代理:
CglibAopProxy
的intercept()
方法被触发。
这两个方法的首要任务是:
a. 根据当前被调用的方法 (doSomething
),从所有已配置的切面中,找出所有适用的通知(Advisors)。
b. 将这些通知包装成一个拦截器链 (List of MethodInterceptor
)。
3. 创建并启动调用“发动机” (ReflectiveMethodInvocation
)
Spring 创建一个 ReflectiveMethodInvocation
对象。这个对象非常重要,它包含了本次调用的所有信息:
- 目标对象 (
UserService
实例) - 目标方法 (
doSomething
方法) - 方法参数 (
args
数组) - 上一步创建的拦截器链
- 一个内部计数器,用于追踪当前执行到链中的哪个位置 (e.g.,
currentInterceptorIndex
)
然后,它会第一次调用 invocation.proceed()
方法,启动整个链条。
4. proceed()
的递归调用 (核心)
proceed()
方法的内部逻辑是整个链路的核心,它是一个巧妙的递归:
a. 检查链条是否走完:
* proceed()
首先会检查内部计数器,看拦截器链是否已经执行完毕。
b. 如果链条未走完:
* 它从拦截器链中取出当前的拦截器(比如,第一个是安全切面的拦截器)。
* 然后调用这个拦截器的 invoke(this)
方法,并把 ReflectiveMethodInvocation
自身作为参数传进去。
* 关键点:每一个拦截器(尤其是 @Around
通知)的实现,在执行完自己的“前置逻辑”后,又会去调用它接收到的那个 invocation
对象的 proceed()
方法。
c. 递归向下:
* 这个 invocation.proceed()
调用会再次触发上面的逻辑:检查计数器、取出下一个拦截器(日志切面的拦截器)、调用它的 invoke()
…
* 这个过程不断重复,形成了一个调用栈,控制权从外层通知传递到内层通知。
d. 如果链条已走完:
* 当计数器发现后面已经没有拦截器了,最后的 proceed()
调用会执行它的终极任务:通过 Java 反射,真正地调用原始目标对象的方法 (target.doSomething()
)。
5. 返回值的“出栈”之旅
a. 目标方法执行完毕后,会返回一个结果(或抛出异常)。
b. 这个结果会返回给最后一个调用 proceed()
的拦截器(日志切面)。
c. 日志切面拿到结果,可以执行它的“后置逻辑”(例如,打印返回值),然后将结果返回。
d. 这个结果又会返回给倒数第二个拦截器(安全切面)。
e. 安全切面执行它的后置逻辑,再将结果返回…
f. 这个过程不断重复,直到结果最终从最外层的拦截器返回,并由代理对象交还给最初的调用方。
图解调用链路 (Onion Model)
Caller|v
Proxy.doSomething()|+-> Jdk/Cglib Interceptor.invoke()|+-> Create ReflectiveMethodInvocation (contains [SecurityInterceptor, LogInterceptor])|+-> invocation.proceed()|+-> SecurityInterceptor.invoke(invocation)|| System.out.println("Security check before...");|+-> invocation.proceed() // <--- 递归调用|+-> LogInterceptor.invoke(invocation)|| System.out.println("Log before...");|+-> invocation.proceed() // <--- 递归调用|+-> No more interceptors, invoke target method!|v+---------------------+| Target.doSomething()| <-- 核心业务+---------------------+|<-- return "Success"|| String result = "Success";| System.out.println("Log after...");| return result;|<--- return "Success"|| String result = "Success";| System.out.println("Security check after...");| return result;|<-- return "Success"||<-- return "Success"|<-- return "Success"|v
Caller
与 @Around
通知的关系
你可能会觉得这个 invocation.proceed()
很眼熟。没错,当你在编写 @Around
通知时,你拿到的 ProceedingJoinPoint
对象,它其实就是 ReflectiveMethodInvocation
的一个门面 (Facade)。
你调用的 pjp.proceed()
,在底层就是触发了我们上面描述的那个递归调用链的下一步。
总结
- 责任链模式: 整个调用链路是一个标准的责任链,每个通知都是链上的一个节点。
- 递归驱动:
ReflectiveMethodInvocation.proceed()
方法通过递归调用,将控制权依次传递给链上的每一个通知,最后到达目标方法。 - 上下文传递:
ReflectiveMethodInvocation
对象作为上下文,贯穿整个调用链,保存着调用状态。 - 洋葱模型: 调用过程像剥洋葱一样,从外层进入,到达核心(目标方法),再从内层一层层返回。