SpringAOP细探
一些概念
为什么要引入 AOP?
Java OOP 存在哪些局限性
- 静态化语言:类结构一旦定义,不容易被修改
- 侵入性扩展:通过继承或组合组织新的类结构
通过 AOP 我们可以把一些非业务逻辑的代码(比如安全检查、监控等代码)从业务中抽取出来,以非入侵的方式与原方法进行协同。
这样可以使得原方法更专注于业务逻辑,代码接口会更加清晰,便于维护。
一、核心概念与基础
- 什么是 AOP?
- AOP(面向切面编程)是一种编程范式,通过横向抽取公共逻辑(如日志、事务、安全等),以非侵入式的方式增强代码复用性和可维护性。
- Spring AOP 的主要应用场景?
- 日志记录、事务管理、权限校验、性能监控、异常处理等横切关注点(Cross-Cutting Concerns)。
- AOP 的核心术语
- Aspect(切面):封装横切逻辑的模块(如
@Aspect
注解的类)。 - Join Point(连接点):程序执行中的某个点(如方法调用、异常抛出)。
- Pointcut(切入点):定义哪些连接点会被切面拦截(通过表达式或注解)。
- Advice(通知):切面在连接点执行的具体动作(如
@Before
,@Around
)。 - Target Object(目标对象):被代理的原始对象。
- Weaving(织入):将切面应用到目标对象生成代理对象的过程。
- Aspect(切面):封装横切逻辑的模块(如
二、实现原理
- Spring AOP 的底层实现原理?
- 基于动态代理:
- JDK 动态代理:针对实现了接口的类(通过
InvocationHandler
)。 - CGLIB 代理:针对未实现接口的类(通过继承生成子类)。
- JDK 动态代理:针对实现了接口的类(通过
- 默认策略:Spring 优先使用 JDK 动态代理,若无接口则使用 CGLIB。
- Spring Boot 2.x+ 默认使用 CGLIB(需配置
spring.aop.proxy-target-class=true
)。
- 基于动态代理:
- JDK 动态代理 vs CGLIB 代理的区别?
特性 JDK 动态代理 CGLIB 依赖 需要接口 无需接口 性能 生成代理较快,调用稍慢 生成代理较慢,调用较快 限制 只能代理接口方法 无法代理 final
方法/类
三、通知(Advice)与切入点(Pointcut)
- Spring AOP 支持的通知类型?
@Before
:方法执行前。@AfterReturning
:方法正常返回后。@AfterThrowing
:方法抛出异常后。@After
(finally
):方法执行后(无论是否异常)。@Around
:包围方法执行,需手动调用ProceedingJoinPoint.proceed()
。
@Around
和@Before
+@After
的区别?@Around
可以完全控制目标方法的执行,甚至阻止其执行;而@Before
/@After
仅能在方法前后插入逻辑。
- 如何定义切入点(Pointcut)表达式?
- 表达式语法:
1
2
3
4
5
6
```
- **通配符示例**:
```java
// 拦截 service 包下所有方法
// 拦截带有 @Log 注解的方法
- 表达式语法:
四、常见问题与解决方案
多个切面的执行顺序如何控制?
- 通过
@Order
注解或实现Ordered
接口,值越小优先级越高(@Before
按升序执行,@After
按降序)。
- 通过
如何解决同类内部方法调用导致 AOP 失效?
- 原因:内部调用不走代理对象。
- 解决方案:
- 通过
AopContext.currentProxy()
获取代理对象(需配置@EnableAspectJAutoProxy(exposeProxy = true)
)。 - 将方法拆分到不同类中。
- 通过
Spring AOP 的局限性?
- 只能拦截
public
方法(除非配置 CGLIB)。 - 无法拦截静态方法、私有方法、final 类/方法。
- 不适用于非 Spring 管理的对象。
- 只能拦截
五、扩展与高级话题
- Spring AOP 与 AspectJ 的区别?
特性 Spring AOP AspectJ 实现方式 动态代理 编译时/类加载时织入 性能 运行时开销较高 编译时优化,性能更高 功能 仅支持方法级别的拦截 支持字段、构造方法等拦截 依赖 轻量,集成于 Spring 需要额外编译器/织入器 - 如何结合自定义注解实现 AOP?
- 定义注解:
1
2
3
4
5
6
7
8
public Log {}
```
- 切面中通过 ` ` 拦截:
```java
public Object logAround(ProceedingJoinPoint joinPoint) { ... }
- 定义注解:
- Spring AOP 如何与事务管理(@Transactional)协作?
@Transactional
基于 Spring AOP 实现,通过代理对象管理事务的开启、提交/回滚。
六、实战技巧
- 调试切入点表达式:使用
AopUtils
工具类判断方法是否被代理。 - 性能优化:避免在频繁调用的方法上使用复杂的 AOP 逻辑。
- 结合 Spring Boot:通过
@EnableAspectJAutoProxy
自动启用 AOP。
AOP 的使用场景
日志场景:
- 诊断上下文,如:log4j 或 logback 中的MDC
- 辅助信息,如:方法执行时间
统计场景: - 方法调用次数
- 执行异常次数
- 数据抽样
- 数值累加
安防场景: - 熔断,如:Netflix Hystrix
- 限流和降级:如:Alibaba Sentinel
- 认证和授权,如:Spring Security
- 监控,如:JMX
性能场景:
- 缓存,如 Spring Cache
- 超时控制
AOP 中几个比较重要的概念
- AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。
- Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。
- Advice:通知,即我们定义的一个切面中的横切逻辑,有“around”,“before”和“after”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。
- Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。
- Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。
- Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。
- AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。
- Target object:目标对象,就是被代理的对象。
有哪几种 AOP 框架
主流 AOP 框架:
- AspectJ:完整的 AOP 实现框架
- Spring AOP:非完整的 AOP 实现框架
Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。
AspectJ 自己有一个编译器,在编译时期可以修改 .class 文件,在运行时也会进行处理。
Spring AOP 有别于其他大多数 AOP 实现框架,目的不是提供最完整的 AOP 实现(尽管 Spring AOP 相当强大);
相反,其目的是在 AOP 实现和 Spring IoC 之间提供紧密的集成,以提供企业级核心特性。
Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP 的所有功能都可以在一个 Spring 应用中。
这种集成不会影响 Spring AOP API 或 AOP Alliance API,保持向后兼容。
什么是 AOP 代理
代理模式是一种结构性设计模式,通过代理类为其他对象提供一种代理以控制对这个对象的访问。
AOP 代理是 AOP 框架中 AOP 的实现,主要分为静态代理和动态代理,如下:
- 静态代理:代理类需要实现被代理类所实现的接口,同时持有被代理类的引用,新增处理逻辑,进行拦截处理,不过方法还是由被代理类的引用所执行。静态代理通常需要由开发人员在编译阶段就定义好,不易于维护。
- 常用 OOP 继承和组合相结合
- AspectJ,在编辑阶段会织入 Java 字节码,且在运行期间会进行增强。
- 动态代理:不会修改字节码,而是在 JVM 内存中根据目标对象新生成一个 Class 对象,这个对象包含了被代理对象的全部方法,并且在其中进行了增强。
- JDK 动态代理
- 字节码提升,例如 CGLIB
JDK 动态代理
基于接口代理,通过反射机制生成一个实现代理接口的类,在调用具体方法时会调用 InvocationHandler 来处理。
需要借助 JDK 的 java.lang.reflect.Proxy
来创建代理对象,调用 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法创建一个代理对象,方法的三个入参分别是:
- ClassLoader loader:用于加载代理对象的 Class 类加载器
- Class<?>[] interfaces:代理对象需要实现的接口
- InvocationHandler h:代理对象的处理器
新生成的代理对象的 Class 对象会继承 Proxy,且实现所有的入参 interfaces 中的接口,在实现的方法中实际是调用入参 InvocationHandler 的 invoke(..) 方法。
CGLIB 动态代理
在运行时,非编译时,来创建一个新的 Class 对象,这种方式称之为字节码提升。
在 Spring 内部有两个字节码提升的框架,ASM(过于底层,直接操作字节码)和 CGLIB(相对于前者更加简便)。
CGLIB 动态代理则是基于类代理(字节码提升),通过 ASM(Java 字节码的操作和分析框架)将被代理类的 class 文件加载进来,修改其字节码生成一个子类。
需要借助于 CGLIB 的 org.springframework.cglib.proxy.Enhancer
类来创建代理对象,设置以下几个属性:
- Class<?> superClass:被代理的类
- Callback callback:回调接口
JDK 动态代理和 CGLIB 动态代理的差异
两者都是在 JVM 运行时期新创建一个 Class 对象,实例化一个代理对象,对目标类(或接口)进行代理。
- JDK 动态代理只能基于接口进行代理,生成的代理类实现了这些接口;
- 而 CGLIB 动态代理则是基于类进行代理的,生成的代理类继承目标类,但是不能代理被 final 修饰的类,也不能重写 final 或者 private 修饰的方法。
- CGLIB 动态代理比 JDK 动态代理复杂许多,性能也相对比较差。
Spring AOP 和 AspectJ 有什么关联?
Spring AOP 和 AspectJ 都是 AOP 的实现框架,AspectJ 是 AOP 的完整实现,Spring AOP 则是部分实现。
AspectJ 有一个很好的编程模型,包含了注解的方式,也包含了特殊语法。Spring 认为 AspectJ 的实现在 AOP 体系里面是完整的,不需要在做自己的一些实现。
Spring AOP 整合 AspectJ 注解与 Spring IoC 容器,比 AspectJ 的使用更加简单,也支持 API 和 XML 的方式进行使用。
不过 Spring AOP 仅支持方法级别的 Pointcut 拦截。
Spring AOP 中有哪些 Advice 类型
- Around Advice:围绕型通知器,需要主动去触发目标方法的执行,这样可以在触发的前后进行相关相关逻辑处理
- Before Advice:前置通知器,在目标方法执行前会被调用
- After Advice:后置通知器
- AfterReturning:在目标方法执行后被调用(方法执行过程中出现异常不会被调用)
- After:在目标方法执行后被调用(执行过程出现异常也会被调用)
- AfterThrowing:执行过程中抛出异常后会被调用(如果异常类型匹配)
执行顺序(Spring 5.2.7 之前的版本):Around “前处理” > Before > 方法执行 > Around “后处理” > After > AfterReturning|AfterThrowing
执行顺序(Spring 5.2.7 开始):Around “前处理” > Before > 方法执行 > AfterReturning|AfterThrowing > After > Around “后处理”
Spring AOP 中 Advisor 接口是什么
Advisor 是 Advice 的一个容器接口,与 Advice 是一对一的关系,它的子接口 PointcutAdvisor 是 Pointcut 和 Advice 的容器接口,将 Pointcut 过滤 Joinpoint 的能力和 Advice 进行整合,这样一来就将两者进行关联起来了。
Pointcut 提供 ClassFilter 和 MethedMatcher,分别支持筛选类和方法,通过 PointcutAdvisor 和 Advice 进行整合,可以说是形成了一个“切面”。
Spring AOP 自动代理的实现
Spring IoC 中 Bean 的加载过程,在整个过程中,Bean 的实例化前和初始化后等生命周期阶段都提供了扩展点,会调用相应的 BeanPostProcessor 处理器对 Bean 进行处理。
当我们开启了 AspectJ 自动代理(例如通过 @EnableAspectJAutoProxy 注解),则会往 IoC 容器中注册一个 AbstractAutoProxyCreator 自动代理对象,该对象实现了几种 BeanPostProcessor,例如在每个 Bean 初始化后会被调用,解析出当前 Spring 上下文中所有的 Advisor(会缓存),如果这个 Bean 需要进行代理,则会通过 JDK 动态代理或者 CGLIB 动态代理创建一个代理对象并返回,所以得到的这个 Bean 实际上是一个代理对象。
这样一来,开发人员只需要配置好 AspectJ 相关信息,Spring 则会进行自动代理,和 Spring IoC 完美地整合在一起。
Spring @EnableAspectJAutoProxy 的原理
使用了 @EnableAspectJAutoProxy
注解则会开启 Spring AOP 自动代理。
该注解上面有一个 @Import(AspectJAutoProxyRegistrar.class)
注解,AspectJAutoProxyRegistrar
实现了 ImportBeanDefinitionRegistrar
这个接口,在实现的方法中会注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理对象(如果没有注册的话),且将其优先级设置为最高,同时解析 @EnableAspectJAutoProxy
注解的配置并进行设置。
这个自动代理对象是一个 BeanPostProcessor
处理器,在 Spring 加载一个 Bean 的过程中,如果它需要被代理,那么会创建一个代理对象(JDK 动态代理或者 CGLIB 动态代理)。
除了注解的方式,也可以通过 <aop:aspectj-autoproxy />
标签开启 Spring AOP 自动代理,原理和注解相同,同样是注册一个自动代理对象。
Spring Configuration Class CGLIB 提升与 AOP 类代理关系
在 Spring 底层 IoC 容器初始化后,会通过 BeanDefinitionRegistryPostProcessor
对其进行后置处理,其中会有一个 ConfigurationClassPostProcessor
处理器会对 @Configuration
标注的 BeanDefinition
进行处理,进行 CGLIB 提升,这样一来对于后续的 Spring AOP 工作就非常简单了,因为这个 Bean 天然就是一个 CGLIB 代理。
在 Spring 5.2 开始 @Configuration 注解中新增了一个 proxyBeanMethods 属性(默认为 true),支持显示的配置是否进行 CGLIB 提升,毕竟进行 CGLIB 提升在启动过程会有一定的性能损耗,且创建的代理对象会占有一定的内存,通过该配置进行关闭,可以减少不必要的麻烦,对 Java 云原生有一定的提升。
Sping AOP 应用到哪些设计模式
- 创建型模式:抽象工厂模式、工厂方法模式、构建器模式、单例模式、原型模式
- 结构型模式:适配器模式、组合模式、装饰器模式、享元模式、代理模式
- 行为型模式:模板方法模式、责任链模式、观察者模式、策略模式、命令模式、状态模式
Spring AOP 在 Spring Framework 内部有哪些应用
- Spring 事件
- Spring 事务
- Spring 数据
- Spring 缓存抽象
- Spring 本地调度
- Spring 整合
- Spring 远程调用
基本涵盖了Spring众多核心分支
使用AOP的注意事项
使用AOP(面向切面编程)时,虽然它能有效解耦横切关注点,但也需要注意以下问题,以避免潜在陷阱:
1. 性能开销
- 代理机制:AOP通过动态代理(JDK动态代理或CGLIB)实现,会引入额外的性能开销,尤其是在高并发场景下。
- 通知链过长:如果切面逻辑复杂或通知链过长,可能会影响方法执行效率。
- 建议:尽量减少切面逻辑的复杂度,避免在性能关键路径上使用过多的AOP。
2. 代理对象的限制
- JDK动态代理的限制:JDK动态代理只能代理实现了接口的类,如果目标类没有实现接口,Spring会使用CGLIB代理。
- CGLIB代理的限制:
- 无法代理
final
类或final
方法,因为CGLIB通过继承生成子类。 - 构造函数会被调用两次(一次是目标对象,一次是代理对象)。
- 无法代理
- 建议:确保目标类和方法适合代理,避免使用
final
修饰符。
3. 切点表达式的精确性
- 过度匹配:如果切点表达式过于宽泛,可能会导致意外的匹配,影响不相关的方法。
- 匹配遗漏:如果切点表达式过于严格,可能会漏掉需要拦截的方法。
- 建议:仔细设计切点表达式,确保其精确匹配目标方法。
4. 通知的执行顺序
- 多个切面的顺序:如果多个切面作用于同一个连接点,通知的执行顺序可能不符合预期。
- 建议:通过
@Order
注解或实现Ordered
接口显式指定切面的执行顺序。
5. 异常处理
- 环绕通知中的异常:在环绕通知中,如果未正确处理异常,可能会导致异常被吞掉或未正确传播。
- 建议:在环绕通知中显式捕获并处理异常,确保异常能够正确传播。
6. 循环依赖问题
- AOP代理与Bean的循环依赖:如果AOP代理的Bean与其他Bean存在循环依赖,可能会导致初始化失败。
- 建议:尽量避免循环依赖,或使用
@Lazy
注解延迟加载。
7. 调试和日志的复杂性
- 调试困难:由于AOP动态代理的存在,调试时可能难以直接追踪到目标对象的实际逻辑。
- 建议:在切面中添加详细的日志,方便排查问题。
8. 事务管理的注意事项
- 事务传播行为:如果切面涉及事务管理,需要确保事务的传播行为符合预期。
- 建议:仔细配置事务切面,确保事务边界清晰。
9. 测试的复杂性
- 单元测试困难:由于AOP逻辑是动态织入的,单元测试时可能需要额外配置来模拟切面行为。
- 建议:使用Spring的测试框架(如
SpringRunner
)进行集成测试,确保AOP逻辑正确。
10. 过度使用AOP
- 滥用AOP:过度使用AOP可能导致代码可读性下降,逻辑分散,难以维护。
- 建议:仅在必要时使用AOP,避免将业务逻辑分散到切面中。
11. 线程安全问题
- 切面中的共享状态:如果切面中使用了共享状态(如成员变量),可能会导致线程安全问题。
- 建议:避免在切面中使用共享状态,或将状态设计为线程安全的。
12. Spring版本兼容性
- AOP特性的差异:不同版本的Spring对AOP的支持可能存在差异,尤其是注解驱动的AOP。
- 建议:确保使用的Spring版本与AOP特性兼容。
Spring AOP 概述
先来看看下面这个AOP API相关总览:
导图分享链接:https://www.processon.com/view/link/673c6b265b61580d25932440?cid=673c39e7ede085743dbf0e5f
上面这张图片列出了 Spring AOP 涉及到的大部分 API,接下来依次简单看一下主要功能:
- Joinpoint:连接点,也就是我们需要执行的目标方法
- Pointcut:切点,提供 ClassFilter 类过滤器和 MethodMatcher 方法匹配器支持对类和方法进行筛选。
- Advice:通知器,一般都是 MethodInterceptor 方法拦截器,不是的话会通过 AdvisorAdapter 适配器转换成对应的 MethodInterceptor 方法拦截器。
- Advisor:Advice 容器接口,与 Advice 是一对一的关系;它的子接口 PointcutAdvisor 相当于一种 Pointcut 和 Advice 的容器,将 Pointcut 过滤 Joinpoint 的能力和 Advice 进行整合,这样一来就将两者关联起来了。
- Advisor 适配器:AdvisorAdapter 是 Advisor 的适配器,当筛选出能够应用于方法的所有 Advisor 后,需要获取对应的 Advice;
- 如果不是 MethodInterceptor 类型,则需要通过 AdvisorAdapter 适配器转换成对应的 MethodInterceptor 方法拦截器。
- AOP 代理对象:AOP 代理对象,由 JDK 动态代理或者 CGLIB 动态代理创建的代理对象;
- 选择哪种动态代理是通过 AopProxyFactory 代理工厂根据目标类来决定的。
- AOP 代理配置:AdvisedSupport 配置管理器中保存了对应代理对象的配置信息,例如满足条件的 Advisor 对象、TargetSource 目标类来源;
- 在创建代理对象的过程,AdvisedSupport 扮演着一个非常重要的角色。
- AOP 代理对象创建:AOP 代理对象的创建分为手动模式和自动模式;不管哪种模式都和 AdvisedSupport 配置管理器有关;-
- 手动模式就是通过 Spring AOP 提供的 API 进行创建;
- 自动模式则是和 Spring IoC 进行整合,在 Bean 的加载过程中如果需要进行代理,则创建对应的代理对象;
- AdvisorChainFactory:Advisor 链路工厂
- AdvisedSupport 配置管理器中保存了代理对象的所有 Advisor 对象,当拦截某个方法时,需要通过 AdvisorChainFactory 筛选出能够应用于该方法的 Advisor 们;
- 另外还需要借助 AdvisorAdapter 适配器获取 Advisor 对应的 MethodInterceptor 方法拦截器,将这些 MethodInterceptor 有序地形成一条链路并返回。
- IntroductionInfo:引介接口,支持代理对象实现目标类没有真正实现的额外的接口;
- 在 Advisor 的子接口 IntroductionAdvisor 中会继承这个 IntroductionInfo 接口,通过 @DeclareParents 注解定义的字段会被解析成 IntroductionAdvisor 对象。
- AOP 代理目标对象来源:目标类来源,和代理对象进行关联,用于获取被代理代理的目标对象;
- 在代理对象中最终的方法执行都需要先通过 TargetSource 获取对应的目标对象,然后执行目标方法。
Spring AOP 自动代理
在正式看AOP功能之前,我们先来回顾一下Bean的加载过程,整个过程中会调用相应的 BeanPostProcessor 对正在创建 Bean 进行处理,例如:
- 在 Bean 的实例化前,会调用
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation(..)
方法进行处理 - 在 Bean 出现循环依赖的情况下,会调用
SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(..)
方法对提前暴露的 Bean 进行处理 - 在 Bean 初始化后,会调用
BeanPostProcessor#postProcessAfterInitialization(..)
方法对初始化好的 Bean 进行处理
Spring AOP 则是通过上面三个切入点进行创建代理对象,实现自动代理。
- 在 Spring AOP 中主要是通过第 3 种 BeanPostProcessor 创建代理对象,在 Bean 初始化后,也就是一个“成熟态”,然后再尝试是否创建一个代理对象;
- 第 2 种方式是为了解决 Bean 循环依赖的问题,虽然 Bean 仅实例化还未初始化,但是出现了循环依赖,不得不在此时创建一个代理对象;
- 第 1 种方式是在 Bean 还没有实例化的时候就提前创建一个代理对象(创建了则不会继续后续的 Bean 的创建过程),例如 RPC 远程调用的实现,因为本地类没有远程能力,可以通过这种方式进行拦截
Spring AOP 自动代理的实现主要由 AbstractAutoProxyCreator 完成,它实现了 BeanPostProcessor、SmartInstantiationAwareBeanPostProcessor
和 InstantiationAwareBeanPostProcessor
三个接口,那么这个类就是 Spring AOP 的入口,在这里将 Advice 织入我们的 Bean 中,创建代理对象。
如何激活 AOP 模块?
如何开启 Spring 的 AOP 模块,首先我们需要引入 spring-aop 和 aspectjweaver 两个模块,然后通过下面的方式开启 AOP:
- 添加
@EnableAspectJAutoProxy
注解 - 添加
<aop:aspectj-autoproxy />
XML 配置
备注:在 Spring Boot 中使用 AOP 可以不需要上面两种配置,因为在 Spring Boot 中当你引入上面两个模块后,默认开启,可以看到下面这个配置类:
1 | package org.springframework.boot.autoconfigure.aop; |
只要存在 EnableAspectJAutoProxy、Aspect、Advice、AnnotatedElement 四个 Class 对象,且 spring.aop.auto 配置为 true(没有配置则为 true),那么就会加载 AopAutoConfiguration 当前这个 Bean,而内部又使用了 @EnableAspectJAutoProxy 注解,那么表示开启 AOP。
类图
- 【重点】
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
:AOP 自动代理的抽象类,完成主要的逻辑实现,提供一些骨架方法交由子类完成 org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator
:仅支持指定List<String> beanNames
完成自动代理,需要指定 interceptorNames 拦截器- 【重点】
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator
:支持从当前 Spring 上下文获取所有 Advisor 对象,存在能应用与 Bean 的 Advisor 则创建代理对象 org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator
:仅支持获取 Spring 内部的 Advisor 对象(BeanDefinition 的角色为 ROLE_INFRASTRUCTURE)org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
:支持配置前缀,只能获取名称已该前缀开头的 Advisor 对象- 【重点】
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
:支持按照 AspectJ 的方式对 Advisor 进行排序 - 【重点】
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
:支持从带有 @AspectJ 注解 Bean 中解析 Advisor 对象
我们主要关注上面【重点】的几个对象,因为 Sping AOP 推荐使用 AspectJ 里面的注解进行 AOP 的配置,你牢牢记住AbstractAutoProxyCreator这个自动代理类。
AbstractAutoProxyCreator
AbstractAutoProxyCreator:AOP 自动代理的抽象类,完成主要的逻辑实现,提供一些骨架方法交由子类完成
相关属性
1 | public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport |
getEarlyBeanReference 方法
getEarlyBeanReference(Object bean, String beanName),用于处理早期暴露的对象,如果有必要的话会创建一个代理对象,该方法接口定义在 SmartInstantiationAwareBeanPostProcessor 中,实现如下:
1 | /** |
对于 getEarlyBeanReference(..) 方法在哪被调用,可能你已经忘记了,这里来回顾一下:
1 | // 在 AbstractAutowireCapableBeanFactory#doCreateBean 创建 Bean 的过程中,实例化后会执行下面步骤 |
上面这个过程是为了处理循环依赖的问题,在 Bean 实例化后就提前暴露这个对象,如果真的出现了循环依赖,如果这个 Bean 需要进行代理,那么就不得不提前为它创建一个代理对象,虽然这个 Bean 还未初始化,不是一个“成熟态”。
postProcessBeforeInstantiation 方法
postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
,Bean 的创建过程中实例化前置处理,也就是允许你在创建 Bean 之前进行处理。如果该方法返回的不为 null,后续 Bean 加载过程不会继续,也就是说这个方法可用于获取一个 Bean 对象。
通常这里用于创建 AOP 代理对象,或者 RPC 远程调用的实现(因为本地类没有远程能力,可以通过这种方式进行拦截)。
1 | /** |
这里先解释一下 TargetSource,这个对象表示目标类的来源,用于获取代理对象的目标对象,上面如果存在 TargetSourceCreator,表示可以创建自定义的 TargetSource,也就需要进行 AOP 代理。
默认情况下是没有 TargetSourceCreator 的,具体使用场景目前还没有接触过。
上面的 4.2 和 4.3 两个方法非常复杂,放在后面进行分析
同样对于 postProcessBeforeInstantiation(..) 方法在哪被调用,可能你已经忘记了,这里来回顾一下:
1 | // 在 AbstractAutowireCapableBeanFactory#createBean 创建 Bean 的过程中,开始前会执行下面步骤 |
上面过程是提供一种扩展点,可以让你在 Bean 创建之前进行相关处理,例如进行 AOP 代理,或者 RPC 远程调用的实现(因为本地类没有远程能力,可以通过这种方式进行拦截)。
postProcessAfterInitialization 方法
postProcessAfterInitialization(@Nullable Object bean, String beanName)
,Bean 的初始化后置处理,在 Bean 初始化后,已经进入一个“成熟态”,那么此时就可以创建 AOP 代理对象了,如果有必要的话。
1 | /** |
对于 postProcessAfterInitialization(..) 方法在哪被调用,可能你已经忘记了,这里来回顾一下
1 | // 在 AbstractAutowireCapableBeanFactory#doCreateBean#initializeBean 创建 Bean 的过程中,属性填充后会进行初始化,初始化后会执行下面的操作 |
上面这个过程在 Bean 初始化后,提供一个扩展点允许对这个 Bean 进行后置处理,此时 Bean 进入一个 “成熟态”,在这里则可以进行 AOP 代理对象的创建
wrapIfNecessary 方法
wrapIfNecessary(Object bean, String beanName, Object cacheKey)
,该方法用于创建 AOP 代理对象,如果有必要的话上面的 getEarlyBeanReference(..)
和 postProcessAfterInitialization(..)
方法都会调用这个方法,如下:
1 | protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { |
小结
至此,我们先总结一下:
Spring AOP 自动代理的入口是 AbstractAutoProxyCreator 对象,其中自动代理的过程主要分为下面两步:
- 筛选出能够应用于当前 Bean 的 Advisor
- 找到了合适 Advisor 则创建一个代理对象, JDK 动态代理或者 CGLIB 动态代理
筛选合适的通知器
接着上文来接着分析筛选合适的通知器的处理过程,包含 @AspectJ 等 AspectJ 注解的解析过程。这里的“通知器”指的是 Advisor 对象。
上文中wrapIfNecessary 方法中的第4步,调用 getAdvicesAndAdvisorsForBean(..)
方法,获取能够应用到当前 Bean 的所有 Advisor(已根据 @Order 排序)
而getAdvicesAndAdvisorsForBean(..)
是一个抽象方法,具体实现交付于子类实现的
1 | // org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean |
筛选出合适的 Advisor 的流程
- 解析出当前 IoC 容器所有 Advisor 对象
- 获取当前 IoC 容器所有 Advisor 类型的 Bean
- 解析当前 IoC 容器中所有带有 @AspectJ 注解的 Bean,具体如下:
@Before|@After|@Around|@AfterReturning|@AfterThrowing
注解的方法解析出对应的 PointcutAdvisor 对象,- 带有
@DeclareParents
注解的字段解析出IntroductionAdvisor
对象 @Around
->AspectJAroundAdvice
,实现了MethodInterceptor
@Before
->AspectJMethodBeforeAdvice
@After
->AspectJAfterAdvice
,实现了MethodInterceptor
@AfterReturning
->AspectJAfterAdvice
@AfterThrowing
->AspectJAfterThrowingAdvice
,实现了MethodInterceptor
- 筛选出能够应用于这个 Bean 的 Advisor 们,主要通过 ClassFilter 类过滤器和 MethodMatcher 方法匹配器进行匹配
- 对筛选出来的 Advisor 进行扩展,例如子类会往首部添加一个 PointcutAdvisor 对象
- 对筛选出来的 Advisor 进行排序
- 不同的 AspectJ 根据 @Order 排序
- 同一个 AspectJ 中不同 Advisor 的排序,优先级是:
AspectJAfterThrowingAdvice > AspectJAfterReturningAdvice > AspectJAfterAdvice > AspectJAroundAdvice > AspectJMethodBeforeAdvice
主要涉及下面几个类:
- AbstractAdvisorAutoProxyCreator:支持从当前 Spring 上下文获取所有 Advisor 对象
- AnnotationAwareAspectJAutoProxyCreator:支持从带有 @AspectJ 注解 Bean 中解析 Advisor 对象
- BeanFactoryAspectJAdvisorsBuilder:Advisor 构建器,用于解析出当前 BeanFactory 中所有带有 @AspectJ 注解的 Bean 中的 Advisor
- ReflectiveAspectJAdvisorFactory:Advisor 工厂,用于解析 @AspectJ 注解的 Bean 中的 Advisor
AbstractAdvisorAutoProxyCreator
AbstractAdvisorAutoProxyCreator:支持从当前 Spring 上下文获取所有 Advisor 对象,存在能应用与 Bean 的 Advisor 则创建代理对象
AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator:支持从带有 @AspectJ 注解 Bean 中解析 Advisor 对象
BeanFactoryAspectJAdvisorsBuilder
BeanFactoryAspectJAdvisorsBuilder,Advisor 构建器,用于解析出当前 BeanFactory 中所有带有 @AspectJ 注解的 Bean 中的 Advisor
ReflectiveAspectJAdvisorFactory
ReflectiveAspectJAdvisorFactory,Advisor 工厂,用于解析 @AspectJ 注解的 Bean 中的 Advisor
创建代理对象
创建代理对象的流程
- 创建一个 ProxyFactory 代理工厂对象,设置需要创建的代理类的配置信息,例如 Advisor 数组和 TargetSource 目标类来源
- 借助 DefaultAopProxyFactory 选择 JdkDynamicAopProxy(JDK 动态代理)还是 ObjenesisCglibAopProxy(CGLIB 动态代理)
- 当 proxy-target-class 为 false 时,优先使用 JDK 动态代理,如果目标类没有实现可代理的接口,那么还是使用 CGLIB 动态代理
- 如果为 true,优先使用 CGLIB 动态代理,如果目标类本身是一个接口,那么还是使用 JDK 动态代理
- 通过 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 创建一个代理对象
- JdkDynamicAopProxy 本身是一个 InvocationHandler 实现类,通过 JDK 的 Proxy.newProxyInstance(..) 创建代理对象
- ObjenesisCglibAopProxy 借助 CGLIB 的 Enhancer 创建代理对象,会设置 Callback 数组和 CallbackFilter 筛选器(选择合适 Callback 处理对应的方法),整个过程相比于 JDK 动态代理更复杂点,主要的实现在 DynamicAdvisedInterceptor 方法拦截器中
AOP 两种代理对象的拦截处理
TODO
AOP 注解驱动与XML配置
TODO