Spring的AOP实现原理
Spring AOP(面向切面编程)的核心实现原理是 动态代理(Dynamic Proxy)。它在运行时为目标对象创建一个代理对象,所有的调用实际上是通过这个代理对象进行的,代理对象在调用真正目标方法的前后(或环绕)织入(Weave)切面逻辑。
以下是详细的实现原理和过程:
核心机制:动态代理
1.运行时生成代理对象:Spring AOP 不会修改原始类的字节码(与 AspectJ 编译时织入不同),而是在运行时为被代理的 Bean 动态生成一个子类(使用 CGLIB)或实现接口的代理类(使用 JDK 动态代理)。
2.拦截方法调用:当你通过代理对象调用一个方法时,代理对象不会直接调用目标对象的方法。相反,它会将调用委派给一个拦截器链(AOP Alliance 标准的 MethodInterceptor)。
3.执行增强逻辑:这个拦截器链由一系列增强(Advice)组成,每个增强代表一个切面逻辑(例如,@Before、@After、@Around 等注解定义的方法)。代理对象负责按正确顺序执行这些增强和目标方法本身。
两种代理方式的具体实现
1.JDK 动态代理 (基于接口)
-
触发条件:当目标对象实现了至少一个接口时,Spring AOP 优先选择这种方式(可以通过配置强制使用 CGLIB)。
-
实现过程:
-
Spring 使用 JDK 内置的 java.lang.reflect.Proxy 类。
-
它会创建一个实现了目标对象所实现的所有接口的新类(代理类)。
-
同时创建一个实现了 InvocationHandler 接口的对象。在这个 InvocationHandler 的 invoke 方法中,封装了调用目标方法及执行拦截器链的逻辑。
-
当客户端代码通过接口调用代理对象的方法时,实际上调用的是 InvocationHandler.invoke 方法。
-
优点:Java 标准库自带,无需额外依赖。
-
缺点:只能代理实现接口的目标类。
2.CGLIB 动态代理 (基于子类化) -
触发条件:
-
目标对象没有实现任何接口。
-
或者配置显式指定强制使用 CGLIB(即使有接口)。
-
实现过程:
-
Spring 使用第三方库 CGLIB。
-
它会动态生成一个目标类的子类(代理类)。
-
覆盖父类(目标类)中的所有非 final 的方法。
-
在覆盖的方法中,将调用委派给一个或多个 MethodInterceptor(类似于 JDK 的 InvocationHandler)。这个 MethodInterceptor 实现中同样封装了调用目标方法(父类方法)和执行拦截器链的逻辑。
-
优点:可以代理没有实现接口的普通类。
-
缺点:
-
需要引入额外的库 (cglib 和可能需要的 asm)。(现代 Spring Boot 自动管理这些依赖)
-
无法代理 final 类或 final/private/static 方法。
-
创建代理对象的速度通常比 JDK 动态代理稍慢(在现代 JVM 上差别已不大),方法调用的性能与 JDK 代理相当或更好。
Spring AOP 如何工作(流程)
1.定义切面:开发者使用 @Aspect 注解或 XML 定义切面类,并在其中使用 @Before、@After、@Around、@AfterReturning、@AfterThrowing 等注解声明通知(Advice)和切入点(Pointcut)。
2.Bean 初始化:Spring 容器在启动时,会查找所有被 @Aspect 注解的 Bean 以及所有配置的切面。
3.创建代理:对于每一个被一个或多个切点匹配到的 Spring Bean(称为目标对象): -
Spring 容器根据上述规则(是否有接口)决定使用 JDK 动态代理还是 CGLIB。
-
使用相应的技术动态创建一个代理对象。
-
这个代理对象取代了原始目标对象被注入到其他依赖它的 Bean 中。
4.方法调用拦截 & 通知链执行: -
当客户端代码通过代理对象调用一个方法时,代理对象被激活。
-
代理对象检查该方法是否被任何已定义的切入点所匹配。
-
如果匹配到了:
-
创建一个 MethodInvocation (或 ProxyMethodInvocation) 对象,它代表当前的方法调用(包含目标对象、方法、参数信息),并持有按顺序排列的通知链(List)。
-
触发MethodInvocation.proceed() 方法。
-
在 proceed() 方法内部:
-
它维护一个当前通知的索引计数器。
-
如果还有通知未执行,则取出下一个通知(MethodInterceptor)并调用其 invoke(this) 方法(this 指向 MethodInvocation 本身)。
-
在环绕通知 (@Around) 中,开发者通常会在其代码中显式调用 proceed() 来驱动调用链继续执行,直到目标方法被调用。
-
前置通知 (@Before) 在目标方法执行前被调用链执行(通常在 proceed() 调用前)。
-
后置通知 (@AfterReturning) 在目标方法正常返回后被调用。
-
异常通知 (@AfterThrowing) 在目标方法抛出异常后被调用。
-
最终通知 (@After) 无论目标方法如何结束(正常返回或异常)都会被调用。
-
最终,当所有前置和环绕通知的前半部分都执行后,会调用真正的目标对象的原始方法。
-
然后,结果(或异常)沿着调用链向上返回,期间执行环绕通知的后半部分、后置通知等。
-
如果一个方法没有被任何切入点匹配,则代理对象直接调用原始目标对象的相应方法。
关键组件协同工作 -
org.springframework.aop.framework.ProxyFactory / ProxyFactoryBean:负责创建 AOP 代理对象的核心工厂类,接受目标对象和通知/切面。
-
org.aopalliance.intercept.MethodInterceptor:AOP 联盟定义的拦截器接口。Spring 的各种通知(如 AspectJMethodBeforeAdvice, AspectJAfterAdvice, AspectJAroundAdvice)以及适配器最终都会实现或适配成 MethodInterceptor 加入到拦截器链中。
-
org.springframework.aop.framework.ReflectiveMethodInvocation:MethodInvocation 的标准实现类,封装了一次方法调用的所有上下文信息(目标对象、方法、参数、代理对象、通知链),并驱动通知链的执行流程。
-
org.springframework.aop.framework.AdvisedSupport:持有构建代理所需的所有信息(目标源、通知链、通知器 Advisor 列表等),是 ProxyFactory 的基类。
重要特点与局限性 -
纯 Java 实现:不依赖特殊的类加载器或编译器,只在运行时生成代理。
-
基于代理:只对 Spring IoC 容器管理的 Bean 有效。直接 new 出来的对象或未被 Spring 管理的对象不受代理影响。
-
仅方法拦截:Spring AOP 只提供方法执行连接点的拦截(如 @Before(“execution(* com.example.service..(…))”))。它无法拦截字段访问修改、构造器调用、静态初始化块等,这些需要 AspectJ。
-
不能代理内部方法调用:在目标对象内部,一个方法 a() 调用另一个方法 b(),这个对 b() 的调用是在目标对象内部发生的,不会经过代理对象,因此 b() 上的通知不会被执行。需要使用 AopContext.currentProxy() 或重构代码。
-
性能:代理对象创建是一次性的,创建后方法调用的开销很小(主要是几次虚方法调用和条件判断)。对于性能极度敏感的场景,CGLIB 在调用时代理性能通常略好于 JDK(创建时略慢),但在现代 JVM 上差异通常可以忽略。
总结一下流程:
客户端代码 -> 调用被代理Bean的方法 -> 实际调用的是动态生成的代理对象 (JDK Proxy或CGLIB子类) -> 代理对象查找匹配该方法的通知链 -> 创建MethodInvocation包装本次调用 -> 驱动MethodInvocation.proceed() -> 按顺序执行拦截器链中的 MethodInterceptor (对应各种通知) -> 最终在链中调用原始目标对象的方法 -> 结果/异常沿链返回 -> 代理对象将结果返回给客户端。
因此,Spring AOP的本质是在运行时为目标Bean创建代理,通过动态代理机制拦截匹配的方法调用,并在目标方法执行前后或周围织入开发者定义的切面逻辑,实现了解耦的非功能性需求。
Spring AOP(面向切面编程)的核心实现原理是动态代理(Dynamic Proxy)。其核心思想是在运行时为需要增强的目标对象(被代理对象)创建一个代理对象,由代理对象控制对原始方法的调用,并在调用前后插入预定义的横切逻辑(如日志、事务管理等)。以下是详细实现原理:
核心机制解析
Spring AOP 通过两种方式实现动态代理,根据目标对象特性自动选择:
1.JDK 动态代理(基于接口) -
适用场景:目标对象实现了至少一个接口。
-
原理:
-
通过 Java 的 java.lang.reflect.Proxy 类生成代理对象。
-
代理类会实现目标对象的所有接口。
-
调用代理对象方法时,请求会被转发到 InvocationHandler.invoke() 方法。
-
在 invoke() 方法中执行拦截器链(包含前置通知、后置通知等)并调用目标方法。
-
优势:JDK 原生支持,无需额外依赖。
-
局限性:只能代理接口方法,无法代理无接口的类。
2.CGLIB 动态代理(基于子类继承) -
适用场景:目标对象未实现接口(如普通类)。
-
原理:
-
通过 ASM 字节码框架动态生成目标类的子类作为代理对象。
-
重写父类(目标类)的非 final 方法。
-
在子类方法中加入拦截逻辑,通过 MethodInterceptor 接口的 intercept() 方法执行通知链。
-
优势:可代理普通类,更灵活。
-
局限性:无法代理 final 类或 final/private/static 方法。
工作流程详解
1.切面定义: -
开发者通过 @Aspect 注解定义切面类,使用 @Before、@After、@Around 等注解声明通知(Advice)和切入点(Pointcut)。
2.代理对象创建(Spring 容器启动时): -
Spring 扫描所有被 @Aspect 标记的类及配置的切面。
-
对每个需代理的 Bean,根据其接口情况选择 JDK 或 CGLIB 动态生成代理对象。
-
代理对象替换原始 Bean 注入到依赖它的其他 Bean 中。
3.方法调用拦截与执行: -
当通过代理对象调用方法时:
-
代理检查方法是否匹配切入点表达式。
-
若匹配,创建 MethodInvocation 对象(封装目标方法、参数及拦截器链)。
-
按顺序执行拦截器链(前置通知 → 目标方法 → 后置通知 → 返回/异常处理)。
-
@Around 通知可手动控制目标方法执行时机(通过调用 ProceedingJoinPoint.proceed())。
关键组件协作 -
ProxyFactory:创建代理对象的核心工厂,接收目标对象和通知列表。
-
MethodInterceptor:AOP 联盟标准接口,所有通知最终适配为此类型加入拦截器链。
-
AdvisedSupport:存储代理配置(目标对象、通知链、切点等)。
-
ReflectiveMethodInvocation:驱动拦截器链执行,维护通知索引链顺序。
Spring AOP 的典型特性与局限 -
优势:
-
纯 Java 实现,无需特殊编译器。
-
与 Spring IoC 容器无缝集成。
-
对开发者透明,通过配置实现解耦。
-
局限性:
-
仅作用于 Spring 管理的 Bean:直接 new 的对象或非 Spring Bean 无法被代理。
-
仅支持方法级拦截:无法拦截字段访问、构造方法等(需 AspectJ)。
-
内部调用问题:同一类中方法 A 调用方法 B 时,B 的通知不生效(因未通过代理对象)。
-
性能:运行时生成代理会有轻微性能开销,但对大多数场景影响可忽略。
总结流程
客户端调用 → 代理对象拦截 → 检查方法是否匹配切点
↓ (匹配)
创建 MethodInvocation → 按顺序执行拦截器链(通知逻辑)
↓
调用目标方法 → 返回结果/异常 → 执行剩余通知(如后置、异常处理)
↓
结果返回客户端
通过动态代理机制,Spring AOP 实现了非侵入式的横切关注点分离,使得业务逻辑与系统级功能(如事务、日志)解耦,大幅提升代码可维护性和复用性。
737

被折叠的 条评论
为什么被折叠?



