-
死磕Spring之AOP篇 - Spring AOP注解驱动与XML配置
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读。
Spring 版本:5.1.14.RELEASE
在开始阅读 Spring AOP 源码之前,需要对 Spring IoC 有一定的了解,可查看我的 《死磕Spring之IoC篇 - 文章导读》 这一系列文章
了解 AOP 相关术语,可先查看 《Spring AOP 常见面试题) 》 这篇文章
该系列其他文章请查看:《死磕 Spring 之 AOP 篇 - 文章导读》
通过前面关于 Spring AOP 的所有文章,我们对 Spring AOP 的整个 AOP 实现逻辑进行了比较详细的分析,例如 Spring AOP 的自动代理,JDK 动态代理或 CGLIB 动态代理两种方式创建的代理对象的拦截处理过程等内容都有讲到。本文将会分析 Spring AOP 的注解驱动,如何引入 AOP 模块,包括如何处理 Spring AOP 的 XML 配置。
在 Spring AOP 中可以通过 @EnableAspectJAutoProxy
注解驱动整个 AOP 模块,我们先一起来看看这个注解。
@EnableAspectJAutoProxy 注解
org.springframework.context.annotation.EnableAspectJAutoProxy
,开启 Spring AOP 整个模块的注解
该注解上面有一个 @Import
注解,它的 value
是 AspectJAutoProxyRegistrar.class
类。
这里先讲一下 @Import
注解的原理,在 Spring IoC 初始化完 BeanFactory 后会有一个 BeanDefinitionRegistryPostProcessor 对其进行后置处理,对配置类(例如 @Configuration
注解标注的 Bean)进行处理,如果这个 BeanDefinition 包含 @Import
注解,则获取注解的值,进行下面的处理:
-
如果是一个 ImportSelector 对象,则调用其
String[] selectImports(AnnotationMetadata)
方法获取需要导入的 Bean 的名称 -
否则,如果是一个 ImportBeanDefinitionRegistrar 对象,先保存起来,在后面调用其
registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)
方法,支持注册相关 Bean - 否则,会注册这个 Bean
关于 @Import
注解不熟悉的小伙伴查看我的另一篇 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 文章
所以说 @EnableAspectJAutoProxy
注解需要标注在能够被 Spring 扫描的类上面,例如 @Configuration
标注的类。其中 AspectJAutoProxyRegistrar 就是 ImportBeanDefinitionRegistrar 的实现类,我们一起来看看。
AspectJAutoProxyRegistrar
org.springframework.context.annotation.AspectJAutoProxyRegistrar
,在 @EnableAspectJAutoProxy
注解中被导入
可以看到它注册 BeanDefinition 的过程如下:
-
通过
AopConfigUtils
注册一个AnnotationAwareAspectJAutoProxyCreator
自动代理对象(如果没有注册的话),设置为优先级最高 -
获取
@EnableAspectJAutoProxy
注解的配置信息 -
如果注解配置信息不为空,则根据配置设置
AnnotationAwareAspectJAutoProxyCreator
的属性-
如果
proxyTargetClass
为true
,则进行设置(开启类代理,也就是开启 CGLIB 动态代理) -
如果
exposeProxy
为true
,则进行设置(需要暴露代理对象,也就是在 Advice 或者被拦截的方法中可以通过 AopContext 获取代理对象)
-
如果
可以看到会注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理对象,是不是很熟悉,就是在前面文章讲到的自动代理对象,那么就开启了 Spring AOP 自动代理,也就是开启了 Spring AOP 整个模块。
AopConfigUtils
org.springframework.aop.config.AopConfigUtils
,AOP 工具类
构造函数
上面定义了 AspectJAwareAdvisorAutoProxyCreator
几种子类的优先级,排在后面优先级越高
registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法
可以看到会注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理 Bean,过程如下:
-
如果
org.springframework.aop.config.internalAutoProxyCreator
已注册- 获取对应的 BeanDefinition 对象
-
如果已注册的
internalAutoProxyCreator
和入参的 Class 不相等,说明可能是继承关系-
获取已注册的
internalAutoProxyCreator
的优先级 -
获取需要注册的
internalAutoProxyCreator
的优先级 -
如果需要注册的优先级更高,那取代已注册的 Class 对象,
InfrastructureAdvisorAutoProxyCreator < AspectJAwareAdvisorAutoProxyCreator < AnnotationAwareAspectJAutoProxyCreator
-
获取已注册的
-
否则,因为已注册,则返回
null
- 否则,没有注册,则创建一个 RootBeanDefinition 对象进行注册
- 设置来源
- 设置为最高优先级
- 设置角色为ROLE_INFRASTRUCTURE,表示是 Spring 框架内部的 Bean
-
注册自动代理的 Bean,名称为
org.springframework.aop.config.internalAutoProxyCreator
- 返回刚注册的 RootBeanDefinition 对象
整个过程很简单,如果已注册自动代理对象,则判断当前需要注册的是否优先级更高,如果更高则修改其对应的 Class 名称;如果没有注册,那么注册这个代理对象,设置优先级最高。
------------------------------------
AOP XML 配置解析过程
再开始之前对于 Spring XML 配置文件不熟悉的小伙伴可以看看我的 《死磕Spring之IoC篇 - 解析自定义标签(XML 文件)》 这篇文章。在 Spring 中对于非默认命名空间的标签需要通过指定的 NamespaceHandler 来处理,在 Spring 的 XML 配置文件中都是在 <beans />
标签内定义数据,需要指定 http://www.springframework.org/schema/beans
作为命名空间,那么对于 http://www.springframework.org/schema/aop
就需要指定 NamespaceHandler 来处理。我们来看到 spring-aop
模块下的 META-INF/spring.handlers
配置文件:
Spring AOP 相关的标签需要 AopNamespaceHandler 进行处理
AopNamespaceHandler
org.springframework.aop.config.AopNamespaceHandler
,继承 NamespaceHandlerSupport
抽象类,Spring AOP 命名空间下的标签处理器
在这个 NamespaceHandler 的 init()
初始化方法中,会往 parsers
中注册几个标签解析器或者装饰器:
-
<aop:config />
:ConfigBeanDefinitionParser -
<aop:aspectj-autoproxy />
:AspectJAutoProxyBeanDefinitionParser -
<aop:scoped-proxy />
:ScopedProxyBeanDefinitionDecorator
继续看到 NamespaceHandlerSupport 这个方法:
会根据标签名称找到对应的 BeanDefinitionParser 解析器进行解析,那么现在思路清晰了,上面不同的标签对应着不同的 BeanDefinitionParser 或者 BeanDefinitionDecorator,我们来看看是怎么处理的。
<aop:aspectj-autoproxy />
这个标签的作用和 @EnableAspectJAutoProxy
注解相同,开启整个 Spring AOP 模块,原理也相同,注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理对象
AspectJAutoProxyBeanDefinitionParser
org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser
,<aop:aspectj-autoproxy />
标签对应 BeanDefinitionParse 解析器
<aop:aspectj-autoproxy />
标签的解析过程先通过 AopNamespaceUtils
工具类注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理对象,然后继续解析 <aop:include />
子标签,用于指定需要开启代理的路径
AopNamespaceUtils
org.springframework.aop.config.AopNamespaceUtils
,Spring AOP XML 配置文件解析工具类
注册 AnnotationAwareAspectJAutoProxyCreator
自动代理对象的过程和 @EnableAspectJAutoProxy
注解类型,这里不再做讲述
<aop:scoped-proxy />
<aop:scoped-proxy />
标签需要定义在 <bean />
中,用来装饰这个 Bean,会生成一个 ScopedProxyFactoryBean 类型的 RootBeanDefinition 对象并注册。ScopedProxyFactoryBean 是一个 FactoryBean,在其 getObject()
方法中返回的是一个代理对象。也就是说 <aop:scoped-proxy />
标签可以将一个 Bean 进行 AOP 代理。
ScopedProxyBeanDefinitionDecorator
org.springframework.aop.config.ScopedProxyBeanDefinitionDecorator
,<aop:scoped-proxy />
标签对应的 BeanDefinitionDecorator 装饰器
<aop:config />
<aop:config>
标签内可以定义 AspectJ 切面的相关信息,例如 Pointcut、Advisor 和 Advice;同时也会注册一个 Spring AOP 自动代理对象(如果有必要的话),不过 是注册 AspectJAwareAdvisorAutoProxyCreator
,只能解析处理 Spring IoC 中 Advisor 类型的 Bean,无法解析 @AspectJ
等相关注解,所以我们最好使用 <aop:aspectj-autoproxy/>
标签来驱动 Spring AOP 模块。
ConfigBeanDefinitionParser
org.springframework.aop.config.ConfigBeanDefinitionParser
,<aop:config />
标签对应的 BeanDefinitionParser 解析器,我们来看到它的 parse(..)
方法
该方法的处理过程如下:
-
解析
<aop:config />
标签,注册 AspectJAwareAdvisorAutoProxyCreator 自动代理对象(如果需要的话),设置为优先级最高过程和
@EnableAspectJAutoProxy
、<aop:aspectj-autoproxy />
的解析过程差不多,这里不再进行展述 -
获取
<aop:config />
的子标签,遍历进行处理-
调用
parsePointcut(..)
方法,处理<aop:pointcut />
子标签,解析出 AspectJExpressionPointcut 对象并注册 -
调用
parseAdvisor(..)
方法,处理<aop:advisor />
子标签,解析出 DefaultBeanFactoryPointcutAdvisor 对象并注册,了指定 Advice 和 Pointcut(如果有) -
调用
parseAspect(..)
方法,处理<aop:aspectj />
子标签,解析出所有的 AspectJPointcutAdvisor 对象并注册,里面包含了 Advice 对象和对应的 Pointcut 对象;同时存在 Pointcut 配置,也会解析出 AspectJExpressionPointcut 对象并注册
-
调用
我们依次来看看你上面三种子标签的处理过程
<aop:pointcut />
处理过程在 parsePointcut(..)
方法中,如下:
解析过程大致如下:
-
获取
<aop:pointcut />
标签的id
和expression
配置 -
根据
expression
表达式创建一个 AspectJExpressionPointcut 类型的 RootBeanDefinition 对象,如下: -
设置来源
-
注册这个 AspectJExpressionPointcut 对象
-
如果
id
配置不为空,则取其作为名称 -
否则,自动生成名称,也就是取
className
-
如果
-
将注册的 BeanDefinition 包装成 ComponentDefinition 放入
parserContext
上下文中,暂时忽略
<aop:advisor />
处理过程在 parseAdvisor(..)
方法中,如下:
解析过程大致如下:
-
解析
<aop:advisor />
标签,创建一个 DefaultBeanFactoryPointcutAdvisor 类型的 RootBeanDefinition 对象,并指定了 Advice -
获取
id
属性 -
注册第
1
步创建的 RootBeanDefinition-
如果
id
不为空,则取其作为名称 -
否则,生成一个名称,也就是
className
-
如果
-
获取这个 Advisor 对应的 Pointcut(也许就是一个 AspectJExpressionPointcut,也可能是引用的 Pointcut 的名称)
-
如果是 AspectJExpressionPointcut,第
1
步创建的 RootBeanDefinition 添加pointcut
属性,指向这个 AspectJExpressionPointcut -
否则,如果是一个引用的 Pointcut 的名称,第
1
步创建的 RootBeanDefinition 添加pointcut
属性,指向这个名称对应的引用
-
如果是 AspectJExpressionPointcut,第
<aop:aspect />
处理过程在 parseAspect(..)
方法中,如下:
解析过程大致如下:
-
获取
id
和ref
属性 -
定义两个集合
beanDefinitions
、beanReferences
,分别保存解析出来的 BeanDefinition 和需要引用的 Bean -
获取所有的
<aop:declare-parents />
子标签,遍历进行处理-
解析
<aop:declare-parents />
子标签,解析出 DeclareParentsAdvisor 对象并注册,添加至beanDefinitions
-
解析
-
获取
<aop:aspectj />
所有的子节点,遍历进行处理-
如果是
<aop:around />、<aop:before />、<aop:after />、<aop:after-returning />、<aop:after-throwing />
标签,则进行处理 -
如果第一次进来,那么就是配置了 Advice,则
ref
必须指定一个 Bean,因为这些 Advice 的method
需要从这个 Bean 中获取-
往
beanReferences
添加需要引用的 Bean
-
往
- 根据 Advice 标签进行解析,创建一个 AspectJPointcutAdvisor 对象,里面包含了 Advice 对象和对应的 Pointcut 对象,并进行注册
-
添加至
beanDefinitions
中
-
如果是
-
将上面创建的所有 Advisor 和引用对象都封装到 AspectComponentDefinition 对象中,并放入
parserContext
上下文中,暂时忽略 -
获取所有的
<aop:pointcut />
子标签,进行遍历处理- 解析出 AspectJExpressionPointcut 对象并注册,前面已经讲过了
上面第 4.3
会解析相关 Advice 标签,我们一起来看看
<aop:aspect /> 的 Advice 子标签
处理过程大致如下:
-
创建 MethodLocatingFactoryBean 类型的 RootBeanDefinition,因为通过标签配置的 Advice 对应的方法在其他 Bean 中,那么可以借助于 FactoryBean 来进行创建
-
获取
targetBeanName
和method
并进行设置 - 设置这个 Bean 是由 Spring 内部合成的
-
获取
-
创建一个 SimpleBeanFactoryAwareAspectInstanceFactory 类型的 RootBeanDefinition
- 设置了 AspectJ 对应的 名称,用于获取这个 AspectJ 的实例对象
- 设置这个 Bean 是由 Spring 内部合成的
-
创建一个 Advice 对象,包含了对应的 Pointcut
-
创建一个 AspectJPointcutAdvisor 类型的 RootBeanDefinition 对象,用于包装上面创建的 Advice,Spring AOP 中的 Advice 都是放入 Advisor “容器” 中
-
注册这个 AspectJPointcutAdvisor,自动生成名字
-
返回这个已注册的 AspectJPointcutAdvisor
------------------------------------
Spring Boot 注解驱动
在 Spring Boot 中使用 Spring AOP 我们通常会这样进行 Maven 引入:
上面这个依赖内部会引入这样两个依赖:
引入相关依赖后,同样可以使用 @EnableAspectJAutoProxy
注解来驱动整个 Spring AOP 依赖,不过在 Spring AOP 中你不需要显示地使用这个注解,因为在 spring-boot-autoconfigure
中,有一个 AOP 自动配置类,我们一起来看看
AopAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
,Spring Boot 中的 AOP 自动配置类
可以看到这个 @Configuration
配置类中有两个条件注解,都是基于 @Conditional
扩展的注解,如下:
-
@ConditionalOnClass
注解:value
中的所有 Class 对象在当前 JVM 必须存在才会注入当前配置类;因为你通过 Spring Boot 引入了
aspectjweaver
这个包,Aspect
、Advice
和AnnotatedElement
三个 Class 对象也就存在了,而EnableAspectJAutoProxy
这个注解本身就存在 Spring 中,所以这个注解是满足条件的 -
@ConditionalOnProperty
注解:指定的配置为true
才会注入当前配置类这个注解会判断
spring.aop.auto
是否为true
,没有配置默认为true
,所以这个注解也是满足条件的
所以得到的结论就是,当你引入 spring-boot-starter-aop
依赖后,Spring Boot 中会注入 AopAutoConfiguration
这个配置类,在这个配置类中的静态内部类使用了 @EnableAspectJAutoProxy
这个注解,那么也就会注册 Spring AOP 自动代理对象。
总结
通过本文,我们可以知道 @EnableAspectJAutoProxy
这个模块驱动注解会借助 @Import
注解注册一个 AnnotationAwareAspectJAutoProxyCreator
自动代理对象,也就开启了 Spring AOP 自动代理,驱动了整个 Spring AOP 模块。
除了注解的方式,Spring 一样也支持 <aop:aspectj-autoproxy />
XML 配置的方式注册一个自动代理对象,驱动整个 Spring AOP 模块;也有 <aop:scoped-proxy />
标签支持装饰某个 Bean,使其进行 AOP 代理。当然,Spring 也支持 <aop:config />
标签配置 AspectJ 切面的相关内容,包括 Poincut、Advice 和 Advisor 等配置。
同时,在使用 Spring Boot 中引入 spring-boot-starter-aop
依赖后,不需要显示地使用 @EnableAspectJAutoProxy
注解来开启 Spring AOP 的自动代理,因为在 spring-boot-autoconfigure
中,有一个 AopAutoConfiguration 自动配置类,会使用这个注解驱动了整个 Spring AOP 模块。
__EOF__