- 示例工程
- 文档
【免费下载链接】spring-reading
涵盖了 Spring 框架的核心概念和关键功能,包括控制反转(IOC)容器的使用,面向切面编程(AOP)的原理与实践,事务管理的方式与实现,Spring MVC 的流程与控制器工作机制,以及 Spring 中数据访问、安全、Boot 自动配置等方面的深入研究。此外,它还包含了 Spring 事件机制的应用、高级主题如缓存抽象和响应式编程,以及对 Spring 源码的编程风格与设计模式的深入探讨。
导读
ProxyMethodInvocation是 Spring AOP 拦截器链得以运转的"骨架"——每一次被代理的方法调用,最终都会封装为一个方法调用对象,在拦截器之间依次传递并最终触达目标方法。本文以 spring-reading 仓库中 spring-aop-proxyMethodInvocation 模块为基础,系统讲解该接口的设计意图、五个核心方法、两大实现类(ReflectiveMethodInvocation与CglibMethodInvocation)的适用场景,并借助模块内完整的可运行示例与关键源码片段,剖析方法调用链的推进原理。读完本文,你将能够理解 Spring AOP 增强逻辑的执行顺序、proceed()的递归调用机制,以及如何在自定义拦截器中安全地操作代理对象与方法参数。
一、什么是 ProxyMethodInvocation
在 Spring AOP 中,一个完整的代理方法调用会经历"代理对象 → 拦截器链 → 目标方法"三个阶段,而贯穿其中的载体就是MethodInvocation(方法调用对象)。ProxyMethodInvocation是 Spring 对 AOP 联盟(AOP Alliance)标准接口org.aopalliance.intercept.MethodInvocation的扩展,它在标准接口的基础上额外暴露了代理对象的访问能力,以及克隆调用、修改参数、附加用户属性等增强能力。
从源码注释可以看出它的核心价值:
扩展了 AOP 联盟
MethodInvocation接口,允许访问通过方法调用所使用的代理对象。如果需要的话,通过此接口可以方便地使用代理对象替换返回值,例如如果调用目标返回了自身对象。
这意味着:当一个目标方法返回this(自身引用)时,拦截器可以通过getProxy()拿到代理对象,将返回值替换为代理,从而保证后续调用依然经过代理链——这是避免"自调用绕过增强"问题(内部方法调用不走代理)的关键手段之一。该接口由 Spring AOP 创始人 Juergen Hoeller 与 Adrian Colyer 设计,自 Spring 1.1.3 版本起引入。
二、ProxyMethodInvocation 的五大核心功能
根据原文档,ProxyMethodInvocation提供了以下五个核心功能,它们共同支撑起整个拦截器链的运转:
获取方法信息:通过
getMethod()获取当前正在调用的方法对象(继承自Joinpoint),包括方法名、参数类型、返回类型等信息,拦截器可据此决定是否执行增强逻辑。获取方法参数:通过
getArguments()获取方法调用时传入的参数数组,允许拦截器检查甚至修改参数。执行下一个拦截器或目标方法:通过
proceed()继续执行拦截器链中的下一个拦截器,或当链走到末尾时调用目标方法。如果proceed()不被调用,拦截器链将被终止,目标方法不会执行——这正是环绕通知(Around Advice)"短路"能力的实现基础。获取目标对象:通过
getThis()获取被代理的目标对象实例,即被拦截的真实业务对象。获取代理对象:通过
getProxy()获取执行当前方法调用的代理对象,这对于在拦截器中将返回值替换为代理对象的场景非常有用(如目标方法返回自身时,避免后续调用脱离代理链)。
三、接口源码逐方法解析
ProxyMethodInvocation接口的完整声明如下(摘自 spring-aop-proxyMethodInvocation 模块文档):
public interface ProxyMethodInvocation extends MethodInvocation { /** 返回执行此方法调用的代理对象 */ Object getProxy(); /** 创建此对象的克隆,每个克隆可调用 proceed() 一次 */ MethodInvocation invocableClone(); /** 创建此对象的克隆,并指定克隆对象所使用的参数(覆盖原始参数) */ MethodInvocation invocableClone(Object... arguments); /** 设置将在此链中的后续调用中使用的参数 */ void setArguments(Object... arguments); /** 向此方法调用添加指定的用户属性和给定的值 */ void setUserAttribute(String key, @Nullable Object value); /** 返回指定用户属性的值 */ @Nullable Object getUserAttribute(String key); }各方法的设计意图如下:
getProxy():返回原始代理对象。结合DelegatingIntroductionInterceptor(引介增强)等场景使用,也可用于返回值替换。invocableClone()/invocableClone(Object... arguments):创建调用的可克隆副本。关键约定:每个克隆只能调用一次proceed()。若在调用proceed()之前进行克隆,则每个克隆可以独立调用一次proceed(),从而多次调用同一个连接点(以及剩余的通知链)——例如重试、异步、分支执行等场景都需要此能力。setArguments(Object...):修改后续链调用中使用的参数,实现运行时参数改写。setUserAttribute/getUserAttribute:向调用对象附加用户自定义属性。注释明确指出:这些属性在 AOP 框架内部不使用,只是作为调用对象的一部分保留,供特殊拦截器在同一调用链的不同拦截器之间传递上下文数据。
四、两大实现类与类关系
ProxyMethodInvocation在实际运行中有两个主要实现(依据代理方式选择):
| 实现类 | 适用场景 | 特点 |
|---|---|---|
ReflectiveMethodInvocation | 目标对象基于接口的JDK 动态代理 | 通过 Java 反射机制执行方法调用,通用性好但性能相对较低 |
CglibMethodInvocation | 目标对象基于类的CGLIB 代理 | 基于 CGLIB 生成子类执行方法调用,通常性能更高,用于代理非接口类型的目标对象 |
值得注意的是,CglibMethodInvocation并不是独立实现,而是继承自ReflectiveMethodInvocation,仅重写了invokeJoinpoint()以改用CglibHelper(方法代理)直调,从而绕开反射的开销。两个实现类的继承关系如下图所示(来自 原文档类关系图):
classDiagram direction BT class CglibMethodInvocation class Invocation { <<Interface>> } class Joinpoint { <<Interface>> } class MethodInvocation { <<Interface>> } class ProxyMethodInvocation { <<Interface>> } class ReflectiveMethodInvocation CglibMethodInvocation --> ReflectiveMethodInvocation Invocation --> Joinpoint MethodInvocation --> Invocation ProxyMethodInvocation --> MethodInvocation ReflectiveMethodInvocation ..> ProxyMethodInvocation从图中可以清晰地看到接口的层级:Joinpoint(连接点)→Invocation(调用)→MethodInvocation(方法调用)→ProxyMethodInvocation(代理方法调用,Spring 扩展),而ReflectiveMethodInvocation是ProxyMethodInvocation的抽象实现基类,CglibMethodInvocation再继承它完成 CGLIB 专用调用。
五、最佳实践:从零构建一条方法调用链
原文档与模块源码给出了一套完整的可运行示例(源码位于 spring-aop-proxyMethodInvocation/src/main/java/com/xcs/spring),演示了如何不依赖 Spring 容器,仅用 JDK 动态代理 + 自定义ReflectiveMethodInvocation子类,手工搭建一条"代理对象 → 拦截器 → 目标方法"的调用链。这有助于理解 Spring AOP 内部真实的运转方式。
5.1 目标对象与调用入口
定义目标接口MyService及其实现MyServiceImpl:
public interface MyService { void foo(); } public class MyServiceImpl implements MyService { @Override public void foo() { System.out.println("foo..."); } }入口类 ProxyMethodInvocationDemo.java 使用Proxy.newProxyInstance()创建 JDK 动态代理:
public class ProxyMethodInvocationDemo { public static void main(String[] args) { // 创建目标对象 MyService target = new MyServiceImpl(); // 获取目标对象的类对象 Class<? extends MyService> clz = target.getClass(); // 创建代理对象,并指定目标对象的类加载器、实现的接口以及调用处理器 MyService proxyObject = (MyService) Proxy.newProxyInstance( clz.getClassLoader(), clz.getInterfaces(), new MyInvocationHandler(target)); // 通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法 proxyObject.foo(); } }5.2 自定义 InvocationHandler:把调用转交给拦截器链
MyInvocationHandler实现 JDK 的InvocationHandler接口,在invoke()中拿到代理对象、方法、参数后,构造一个MyReflectiveMethodInvocation并调用proceed(),从而把"裸的 JDK 代理调用"接入 AOP 风格的拦截器链:
class MyInvocationHandler implements InvocationHandler { private final Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 创建 MyReflectiveMethodInvocation 对象,用于执行方法调用链 MyReflectiveMethodInvocation invocation = new MyReflectiveMethodInvocation( proxy, target, method, args, target.getClass(), List.of(new MyMethodInterceptor())); // 执行方法调用链,并返回结果 return invocation.proceed(); } }注意这里传入的拦截器链是List.of(new MyMethodInterceptor())——在实际 Spring 框架中,这个列表由AdvisorChainFactory根据匹配的切面(Advisor)计算生成,包含MethodInterceptor与InterceptorAndDynamicMethodMatcher两类元素。
5.3 自定义 ReflectiveMethodInvocation:公开构造器
ReflectiveMethodInvocation的构造器是protected的,因此示例自定义子类将其公开,以便在外部构造调用对象并附加自定义逻辑:
public class MyReflectiveMethodInvocation extends ReflectiveMethodInvocation { public MyReflectiveMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); } }5.4 自定义拦截器:环绕增强
MyMethodInterceptor实现 AOP 联盟的MethodInterceptor接口,在调用proceed()前后分别输出日志,演示典型的前置/后置增强模式:
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 在方法调用之前执行的逻辑 System.out.println("Before Method " + invocation.getMethod().getName()); // 调用原始方法(继续推进拦截器链) Object result = invocation.proceed(); // 在方法调用之后执行的逻辑 System.out.println("After Method " + invocation.getMethod().getName()); return result; } }5.5 运行结果
运行ProxyMethodInvocationDemo后,输出如下,表明拦截器成功拦截了foo()方法并在执行前后添加了额外逻辑:
Before Method foo foo... After Method foo整个调用链的时序为:代理对象.foo() → MyInvocationHandler.invoke() → MyReflectiveMethodInvocation.proceed() → MyMethodInterceptor.invoke()(打印 Before)→ proceed() 递归推进 → 反射调用目标方法(打印 foo...)→ 返回后打印 After。
六、源码分析:拦截器链如何推进
6.1 构造阶段:参数初始化与桥接方法处理
在org.springframework.aop.framework.ReflectiveMethodInvocation#ReflectiveMethodInvocation构造函数中,除了简单的成员赋值,有两个值得注意的处理:
protected ReflectiveMethodInvocation( Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments, @Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; // 找到桥接方法:将编译器生成的桥接方法解析为实际方法 this.method = BridgeMethodResolver.findBridgedMethod(method); // 调整参数:必要时用空数组替换 null 参数 this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments); this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; }BridgeMethodResolver.findBridgedMethod(method):处理 Java 泛型桥接方法(如泛型接口T get()被子类用具体类型实现时,编译器生成的桥接方法),确保拦截器看到的是业务真正定义的方法签名。AopProxyUtils.adaptArgumentsIfNecessary(method, arguments):当参数为null时适配为空数组,避免后续反射调用出错。- 传入的
interceptorsAndDynamicMethodMatchers列表约定:其中包含的MethodMatchers应已尽可能完成静态匹配;源码注释也提示,若只用于静态切入点,改为传数组可提升约 10% 的性能,但会复杂化代码。
6.2 proceed():拦截器链的递归引擎
proceed()是整个方法调用链的"引擎",其逻辑分为三种情况:
@Override @Nullable public Object proceed() throws Throwable { // 我们从索引 -1 开始并提前递增 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { // ① 到达拦截器链末尾,调用连接点(目标方法) return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { // ② 动态方法匹配器:运行时评估切入点 InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) { // 匹配成功,调用拦截器的 invoke 方法 return dm.interceptor.invoke(this); } else { // 动态匹配失败,跳过当前拦截器,递归调用链中的下一个 return proceed(); } } else { // ③ 普通拦截器:直接调用 invoke 方法 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }理解要点:
- 索引从
-1开始并提前递增(++this.currentInterceptorIndex),保证第一个proceed()取到的是链上第 0 个拦截器。 - 当索引到达
size() - 1时说明链已走完,转入invokeJoinpoint()执行目标方法——也就是说,目标方法的执行一定发生在最后一个拦截器的proceed()调用中。 - 每个拦截器的
invoke(this)收到的是同一个ReflectiveMethodInvocation实例,当它再次调用proceed()时,索引继续后移,从而形成"递归推进 + 逐层回卷"的效果,天然支持 Around 通知的前后逻辑对称执行。 - 若某个拦截器不调用
proceed(),链在此中断,目标方法不会执行——例如ExposeInvocationInterceptor之后的短路型拦截器。
6.3 invokeJoinpoint():反射调用目标方法
当链走到尽头时,invokeJoinpoint()负责真正触达业务方法。该方法是protected的,子类可重写以使用自定义调用方式——这正是CglibMethodInvocation性能优化点的所在:
@Nullable protected Object invokeJoinpoint() throws Throwable { // 使用反射调用连接点 return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments); }6.4 AopUtils.invokeJoinpointUsingReflection:反射的健壮封装
底层反射调用由org.springframework.aop.support.AopUtils#invokeJoinpointUsingReflection完成,它做了三件关键的事:
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args) throws Throwable { try { // 设置方法可访问性(处理非 public 方法) ReflectionUtils.makeAccessible(method); // 调用方法 return method.invoke(target, args); } catch (InvocationTargetException ex) { // 目标方法抛出的已检查异常:必须原样重新抛出,客户端不会看到拦截器 throw ex.getTargetException(); } catch (IllegalArgumentException ex) { // 参数错误 → AOP 配置无效 throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + method + "] on target [" + target + "]", ex); } catch (IllegalAccessException ex) { // 无法访问方法 throw new AopInvocationException("Could not access method [" + method + "]", ex); } }其中最重要的设计是:捕获InvocationTargetException后必须重新抛出其目标异常(getTargetException()),以保证业务方法抛出的异常在拦截器链中保持"原样可见",不会因为反射包装而失真——这保证了你捕获到的异常类型与业务代码声明完全一致。
七、总结:ProxyMethodInvocation 在 Spring AOP 中的位置
回顾整个机制,ProxyMethodInvocation处于代理对象与目标方法之间的"传导层":
- 代理层(
ProxyFactory→AopProxy→ JDK/CGLIB 代理对象)负责拦截方法入口; - 调用层(
ProxyMethodInvocation及其实现ReflectiveMethodInvocation/CglibMethodInvocation)负责承载调用上下文(代理、目标、方法、参数、拦截器链)并通过proceed()推进; - 增强层(
MethodInterceptor)在链上注册的前后位置执行横切逻辑。
它与仓库中其他 AOP 模块的关系也可以在此串联:ProxyFactory(spring-aop-proxyFactory)通过DefaultAopProxyFactory决定代理方式并创建代理对象,AdvisorChainFactory负责把匹配的 Advisor 组装成拦截器链传入ReflectiveMethodInvocation,而ProxyMethodInvocation则在每个方法调用发生时承载并推进这条链。理解了它,你就理解了 Spring AOP 增强逻辑执行顺序的本质。
- 示例工程
- 文档
【免费下载链接】spring-reading
涵盖了 Spring 框架的核心概念和关键功能,包括控制反转(IOC)容器的使用,面向切面编程(AOP)的原理与实践,事务管理的方式与实现,Spring MVC 的流程与控制器工作机制,以及 Spring 中数据访问、安全、Boot 自动配置等方面的深入研究。此外,它还包含了 Spring 事件机制的应用、高级主题如缓存抽象和响应式编程,以及对 Spring 源码的编程风格与设计模式的深入探讨。
相关推荐
spring-reading 源码解读:深入剖析 Spring AOP 的 TargetSource 目标源机制与连接池实战
spring reading 源码解读:深入剖析 Spring AOP 的 TargetSource 目标源机制与连接池实战 TargetSource 是 Sp
示例工程文档Spring 源码阅读:深入剖析 MethodMatcher 方法匹配器——Spring AOP 切点匹配的核心接口
Spring 源码阅读:深入剖析 MethodMatcher 方法匹配器——Spring AOP 切点匹配的核心接口 MethodMatcher 是 Sprin
示例工程文档深入理解mini-spring: PropertyValues属性注入核心机制与实现
深入理解mini spring: PropertyValues属性注入核心机制与实现 引言:告别繁琐的手动注入 在传统Java开发中,你是否还在忍受这样的痛苦:
后端教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考