該系列文章是本人在學習 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 整個模組的註解
/**
* @since 3.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* 是否開啟類代理,也就是是否開啟 CGLIB 動態代理
*/
boolean proxyTargetClass() default false;
/**
* 是否需要暴露代理物件
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
該註解上面有一個 @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
註解中被匯入
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// <1> 註冊一個 AnnotationAwareAspectJAutoProxyCreator 自動代理物件(如果沒有註冊的話),設定為優先順序最高
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// <2> 獲取 @EnableAspectJAutoProxy 註解的配置資訊
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
// <3> 如果註解配置資訊不為空,則根據配置設定 AnnotationAwareAspectJAutoProxyCreator 的屬性
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
// 設定 `proxyTargetClass` 為 `true`(開啟類代理,也就是開啟 CGLIB 動態代理)
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
// 設定 `exposeProxy` 為 `true`(需要暴露代理物件,也就是在 Advice 或者被攔截的方法中可以通過 AopContext 獲取代理物件)
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
可以看到它註冊 BeanDefinition 的過程如下:
- 通過
AopConfigUtils
註冊一個AnnotationAwareAspectJAutoProxyCreator
自動代理物件(如果沒有註冊的話),設定為優先順序最高 - 獲取
@EnableAspectJAutoProxy
註解的配置資訊 - 如果註解配置資訊不為空,則根據配置設定
AnnotationAwareAspectJAutoProxyCreator
的屬性- 如果
proxyTargetClass
為true
,則進行設定(開啟類代理,也就是開啟 CGLIB 動態代理) - 如果
exposeProxy
為true
,則進行設定(需要暴露代理物件,也就是在 Advice 或者被攔截的方法中可以通過 AopContext 獲取代理物件)
- 如果
可以看到會註冊一個 AnnotationAwareAspectJAutoProxyCreator
自動代理物件,是不是很熟悉,就是在前面文章講到的自動代理物件,那麼就開啟了 Spring AOP 自動代理,也就是開啟了 Spring AOP 整個模組。
AopConfigUtils
org.springframework.aop.config.AopConfigUtils
,AOP 工具類
建構函式
public abstract class AopConfigUtils {
/**
* The bean name of the internally managed auto-proxy creator.
*/
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
"org.springframework.aop.config.internalAutoProxyCreator";
/**
* Stores the auto proxy creator classes in escalation order.
*/
private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);
static {
// Set up the escalation list...
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}
}
上面定義了 AspectJAwareAdvisorAutoProxyCreator
幾種子類的優先順序,排在後面優先順序越高
registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 註冊 AnnotationAwareAspectJAutoProxyCreator Bean
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// <1> 如果 `org.springframework.aop.config.internalAutoProxyCreator` 已註冊
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// <1.1> 獲取對應的 BeanDefinition 物件
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
// <1.2> 如果已註冊的 `internalAutoProxyCreator` 和入參的 Class 不相等,說明可能是繼承關係
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
// <1.2.1> 獲取已註冊的 `internalAutoProxyCreator` 的優先順序
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
// <1.2.2> 獲取需要註冊的 `internalAutoProxyCreator` 的優先順序
int requiredPriority = findPriorityForClass(cls);
// InfrastructureAdvisorAutoProxyCreator < AspectJAwareAdvisorAutoProxyCreator < AnnotationAwareAspectJAutoProxyCreator
// 三者都是 AbstractAutoProxyCreator 自動代理物件的子類
if (currentPriority < requiredPriority) {
// <1.2.3> 如果需要註冊的優先順序更高,那取代已註冊的 Class 物件
apcDefinition.setBeanClassName(cls.getName());
}
}
// <1.3> 因為已註冊,則返回 `null`
return null;
}
// <2> 沒有註冊,則建立一個 RootBeanDefinition 物件進行註冊
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
// <3> 設定來源
beanDefinition.setSource(source);
// <4> 設定為最高優先順序
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
// <5> 設定角色為**ROLE_INFRASTRUCTURE**,表示是 Spring 框架內部的 Bean
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// <6> 註冊自動代理的 Bean,名稱為 `org.springframework.aop.config.internalAutoProxyCreator`
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
// <7> 返回剛註冊的 RootBeanDefinition 物件
return beanDefinition;
}
可以看到會註冊一個 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
配置檔案:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
Spring AOP 相關的標籤需要 AopNamespaceHandler 進行處理
AopNamespaceHandler
org.springframework.aop.config.AopNamespaceHandler
,繼承 NamespaceHandlerSupport
抽象類,Spring AOP 名稱空間下的標籤處理器
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
// <aop:config /> 標籤的解析器
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
// <aop:aspectj-autoproxy /> 標籤的解析器
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
// <aop:scoped-proxy /> 標籤的解析器
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
this.decorators.put(elementName, dec);
}
}
在這個 NamespaceHandler 的 init()
初始化方法中,會往 parsers
中註冊幾個標籤解析器或者裝飾器:
<aop:config />
:ConfigBeanDefinitionParser<aop:aspectj-autoproxy />
:AspectJAutoProxyBeanDefinitionParser<aop:scoped-proxy />
:ScopedProxyBeanDefinitionDecorator
繼續看到 NamespaceHandlerSupport 這個方法:
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// <1> 獲得元素對應的 BeanDefinitionParser 物件
BeanDefinitionParser parser = findParserForElement(element, parserContext);
// <2> 執行解析
return (parser != null ? parser.parse(element, parserContext) : null);
}
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 獲得元素名
String localName = parserContext.getDelegate().getLocalName(element);
// 獲得 BeanDefinitionParser 物件
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
@Override
@Nullable
public BeanDefinitionHolder decorate(
Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
// 根據標籤名獲取 BeanDefinitionDecorator 物件
BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
}
@Nullable
private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
BeanDefinitionDecorator decorator = null;
// 獲得元素名
String localName = parserContext.getDelegate().getLocalName(node);
if (node instanceof Element) {
decorator = this.decorators.get(localName);
}
else if (node instanceof Attr) {
decorator = this.attributeDecorators.get(localName);
}
else {
parserContext.getReaderContext().fatal(
"Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node);
}
if (decorator == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " +
(node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node);
}
return decorator;
}
會根據標籤名稱找到對應的 BeanDefinitionParser 解析器進行解析,那麼現在思路清晰了,上面不同的標籤對應著不同的 BeanDefinitionParser 或者 BeanDefinitionDecorator,我們來看看是怎麼處理的。
<aop:aspectj-autoproxy />
<beans>
<aop:aspectj-autoproxy proxy-target-class="false" expose-proxy="false" />
</beans>
這個標籤的作用和 @EnableAspectJAutoProxy
註解相同,開啟整個 Spring AOP 模組,原理也相同,註冊一個 AnnotationAwareAspectJAutoProxyCreator
自動代理物件
AspectJAutoProxyBeanDefinitionParser
org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser
,<aop:aspectj-autoproxy />
標籤對應 BeanDefinitionParse 解析器
class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 解析 <aop:aspectj-autoproxy /> 標籤
// 註冊 AnnotationAwareAspectJAutoProxyCreator 自動代理物件(如果沒有註冊的話),設定為優先順序最高
// 過程和 @EnableAspectJAutoProxy
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 解析 <aop:include /> 子標籤,用於指定需要開啟代理的路徑
extendBeanDefinition(element, parserContext);
return null;
}
private void extendBeanDefinition(Element element, ParserContext parserContext) {
BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
if (element.hasChildNodes()) {
addIncludePatterns(element, parserContext, beanDef);
}
}
private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
ManagedList<TypedStringValue> includePatterns = new ManagedList<>();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element) {
Element includeElement = (Element) node;
TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
valueHolder.setSource(parserContext.extractSource(includeElement));
includePatterns.add(valueHolder);
}
}
if (!includePatterns.isEmpty()) {
includePatterns.setSource(parserContext.extractSource(element));
beanDef.getPropertyValues().add("includePatterns", includePatterns);
}
}
}
<aop:aspectj-autoproxy />
標籤的解析過程先通過 AopNamespaceUtils
工具類註冊一個 AnnotationAwareAspectJAutoProxyCreator
自動代理物件,然後繼續解析 <aop:include />
子標籤,用於指定需要開啟代理的路徑
AopNamespaceUtils
org.springframework.aop.config.AopNamespaceUtils
,Spring AOP XML 配置檔案解析工具類
public abstract class AopNamespaceUtils {
public static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class";
private static final String EXPOSE_PROXY_ATTRIBUTE = "expose-proxy";
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
// <1> 註冊 AnnotationAwareAspectJAutoProxyCreator 自動代理物件(如果沒有註冊的話),設定為優先順序最高
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// <2> 則根據 <aop:aspectj-autoproxy /> 標籤的配置設定 AnnotationAwareAspectJAutoProxyCreator 的屬性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// <3> 將註冊的 BeanDefinition 也放入 `parserContext` 上下文中
registerComponentIfNecessary(beanDefinition, parserContext);
}
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
// 如果 <aop:aspectj-autoproxy /> 標籤不為空
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
// 設定 `proxyTargetClass` 為 `true`(開啟類代理,也就是開啟 CGLIB 動態代理)
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
// 設定 `exposeProxy` 為 `true`(需要暴露代理物件,也就是在 Advice 或者被攔截的方法中可以通過 AopContext 獲取代理物件)
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
if (beanDefinition != null) {
parserContext.registerComponent(
new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
}
}
}
註冊 AnnotationAwareAspectJAutoProxyCreator
自動代理物件的過程和 @EnableAspectJAutoProxy
註解型別,這裡不再做講述
<aop:scoped-proxy />
<beans>
<bean id="echoService" class="org.geekbang.thinking.in.spring.aop.overview.DefaultEchoService" >
<aop:scoped-proxy />
</bean>
</beans>
<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 裝飾器
class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator {
private static final String PROXY_TARGET_CLASS = "proxy-target-class";
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
boolean proxyTargetClass = true;
if (node instanceof Element) {
Element ele = (Element) node;
if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));
}
}
// Register the original bean definition as it will be referenced by the scoped proxy
// and is relevant for tooling (validation, navigation).
// 建立一個 ScopedProxyFactoryBean 型別的 RootBeanDefinition 物件並註冊
// ScopedProxyFactoryBean 用於裝飾 `definition`,進行 AOP 代理
BeanDefinitionHolder holder = ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);
String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName());
parserContext.getReaderContext().fireComponentRegistered(
new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName));
return holder;
}
}
<aop:config />
<beans>
<aop:aspectj-autoproxy/>
<bean id="aspectXmlConfig" class="org.geekbang.thinking.in.spring.aop.features.aspect.AspectXmlConfig"/>
<aop:config>
<aop:pointcut id="anyPublicStringMethod" expression="execution(public String *(..))"/>
<aop:advisor advice-ref="echoServiceMethodInterceptor" pointcut-ref="anyPublicStringMethod" />
<aop:aspect id="AspectXmlConfig" ref="aspectXmlConfig">
<aop:pointcut id="anyPublicMethod" expression="execution(public * *(..))"/>
<aop:around method="aroundAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:before method="beforeAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:before method="beforeAnyPublicMethod" pointcut="execution(public * *(..))"/>
<aop:after method="finalizeAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:after-returning method="afterAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:after-throwing method="afterThrowingAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
</aop:aspect>
</aop:config>
</beans>
<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(..)
方法
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// <1> 解析 <aop:config /> 標籤
// 註冊 AspectJAwareAdvisorAutoProxyCreator 自動代理物件(如果需要的話),設定為優先順序最高
// 過程和 @EnableAspectJAutoProxy、<aop:aspectj-autoproxy /> 差不多
configureAutoProxyCreator(parserContext, element);
// <2> 獲取 <aop:config /> 的子標籤,遍歷進行處理
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
// 獲取子標籤的名稱
String localName = parserContext.getDelegate().getLocalName(elt);
if (POINTCUT.equals(localName)) {
// <2.1> 處理 <aop:pointcut /> 子標籤,解析出 AspectJExpressionPointcut 物件並註冊
parsePointcut(elt, parserContext);
}
else if (ADVISOR.equals(localName)) {
// <2.2> 處理 <aop:advisor /> 子標籤,解析出 DefaultBeanFactoryPointcutAdvisor 物件並註冊,了指定 Advice 和 Pointcut(如果有)
parseAdvisor(elt, parserContext);
}
else if (ASPECT.equals(localName)) {
// <2.3> 處理 <aop:aspectj /> 子標籤,解析出所有的 AspectJPointcutAdvisor 物件並註冊,裡面包含了 Advice 物件和對應的 Pointcut 物件
// 同時存在 Pointcut 配置,也會解析出 AspectJExpressionPointcut 物件並註冊
parseAspect(elt, parserContext);
}
}
// <3> 將 `parserContext` 上下文中已註冊的 BeanDefinition 合併到上面 `compositeDef` 中(暫時忽略)
parserContext.popAndRegisterContainingComponent();
return null;
}
該方法的處理過程如下:
-
解析
<aop:config />
標籤,註冊 AspectJAwareAdvisorAutoProxyCreator 自動代理物件(如果需要的話),設定為優先順序最高private void configureAutoProxyCreator(ParserContext parserContext, Element element) { // 註冊 AspectJAwareAdvisorAutoProxyCreator 自動代理物件(如果需要的話) AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element); }
過程和
@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 />
<beans>
<aop:aspectj-autoproxy/>
<bean id="aspectXmlConfig" class="org.geekbang.thinking.in.spring.aop.features.aspect.AspectXmlConfig"/>
<aop:config>
<aop:pointcut id="anyPublicStringMethod" expression="execution(public String *(..))"/>
</aop:config>
</beans>
處理過程在 parsePointcut(..)
方法中,如下:
// ConfigBeanDefinitionParser.java
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
// <1> 獲取 <aop:pointcut /> 標籤的 `id` 和 `expression` 配置
String id = pointcutElement.getAttribute(ID);
String expression = pointcutElement.getAttribute(EXPRESSION);
AbstractBeanDefinition pointcutDefinition = null;
try {
this.parseState.push(new PointcutEntry(id));
// <2> 建立一個 AspectJExpressionPointcut 型別的 RootBeanDefinition 物件
pointcutDefinition = createPointcutDefinition(expression);
// <3> 設定來源
pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
String pointcutBeanName = id;
// <4> 註冊這個 AspectJExpressionPointcut 物件
if (StringUtils.hasText(pointcutBeanName)) {
// <4.1> 如果 `id` 配置不為空,則取其作為名稱
parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
}
else {
// <4.2> 否則,自動生成名稱,也就是取 `className`
pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
}
// <5> 將註冊的 BeanDefinition 包裝成 ComponentDefinition 放入 `parserContext` 上下文中,暫時忽略
parserContext.registerComponent(
new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
}
finally {
this.parseState.pop();
}
return pointcutDefinition;
}
解析過程大致如下:
-
獲取
<aop:pointcut />
標籤的id
和expression
配置 -
根據
expression
表示式建立一個 AspectJExpressionPointcut 型別的 RootBeanDefinition 物件,如下:protected AbstractBeanDefinition createPointcutDefinition(String expression) { // <1> 建立一個 AspectJExpressionPointcut 型別的 RootBeanDefinition 物件 RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class); // <2> 設定為原型模式,需要保證每次獲取到的 Pointcut 物件都是新的,防止在某些地方被修改而影響到其他地方 beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); // <3> 設定為是 Spring 內部合成的 beanDefinition.setSynthetic(true); // <4> 新增 `expression` 屬性值 beanDefinition.getPropertyValues().add(EXPRESSION, expression); // <5> 返回剛建立的 RootBeanDefinition 物件 return beanDefinition; }
-
設定來源
-
註冊這個 AspectJExpressionPointcut 物件
- 如果
id
配置不為空,則取其作為名稱 - 否則,自動生成名稱,也就是取
className
- 如果
-
將註冊的 BeanDefinition 包裝成 ComponentDefinition 放入
parserContext
上下文中,暫時忽略
<aop:advisor />
<beans>
<aop:aspectj-autoproxy/>
<bean id="aspectXmlConfig" class="org.geekbang.thinking.in.spring.aop.features.aspect.AspectXmlConfig"/>
<aop:config>
<aop:pointcut id="anyPublicStringMethod" expression="execution(public String *(..))"/>
<aop:advisor advice-ref="echoServiceMethodInterceptor" pointcut-ref="anyPublicStringMethod" />
</aop:config>
</beans>
處理過程在 parseAdvisor(..)
方法中,如下:
// ConfigBeanDefinitionParser.java
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
// <1> 解析 <aop:advisor /> 標籤
// 建立一個 DefaultBeanFactoryPointcutAdvisor 型別的 RootBeanDefinition 物件,並指定了 Advice
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
// <2> 獲取 `id` 屬性
String id = advisorElement.getAttribute(ID);
try {
this.parseState.push(new AdvisorEntry(id));
String advisorBeanName = id;
// <3> 註冊第 `1` 步建立的 RootBeanDefinition
if (StringUtils.hasText(advisorBeanName)) {
// <3.1> 如果 `id` 不為空,則取其作為名稱
parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
// <3.2> 否則,生成一個名稱,也就是 `className`
advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
}
// <4> 獲取這個 Advisor 對應的 Pointcut(也許就是一個 AspectJExpressionPointcut,也可能是引用的 Pointcut 的名稱)
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
// <4.1> 如果是 AspectJExpressionPointcut
if (pointcut instanceof BeanDefinition) {
// 第 `1` 步建立的 RootBeanDefinition 新增 `pointcut` 屬性,指向這個 AspectJExpressionPointcut
advisorDef.getPropertyValues().add(POINTCUT, pointcut);
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
}
// <4.2> 否則,如果是一個引用的 Pointcut 的名稱
else if (pointcut instanceof String) {
// 第 `1` 步建立的 RootBeanDefinition 新增 `pointcut` 屬性,指向這個名稱對應的引用
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef));
}
}
finally {
this.parseState.pop();
}
}
解析過程大致如下:
-
解析
<aop:advisor />
標籤,建立一個 DefaultBeanFactoryPointcutAdvisor 型別的 RootBeanDefinition 物件,並指定了 Adviceprivate AbstractBeanDefinition createAdvisorBeanDefinition(Element advisorElement, ParserContext parserContext) { // <1> 建立一個 DefaultBeanFactoryPointcutAdvisor 型別的 RootBeanDefinition 物件 RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class); // <2> 設定來源 advisorDefinition.setSource(parserContext.extractSource(advisorElement)); // <3> 獲取 `advice-ref` 屬性配置,必須配置一個對應的 Advice String adviceRef = advisorElement.getAttribute(ADVICE_REF); if (!StringUtils.hasText(adviceRef)) { parserContext.getReaderContext().error( "'advice-ref' attribute contains empty value.", advisorElement, this.parseState.snapshot()); } else { // <4> 將 `advice-ref` 新增至 `adviceBeanName` 屬性,也就是指向這個 Advice 引用 advisorDefinition.getPropertyValues().add( ADVICE_BEAN_NAME, new RuntimeBeanNameReference(adviceRef)); } // <5> 根據 `order` 配置為 RootBeanDefinition 設定優先順序 if (advisorElement.hasAttribute(ORDER_PROPERTY)) { advisorDefinition.getPropertyValues().add( ORDER_PROPERTY, advisorElement.getAttribute(ORDER_PROPERTY)); } // <6> 返回剛建立的 RootBeanDefinition return advisorDefinition; }
-
獲取
id
屬性 -
註冊第
1
步建立的 RootBeanDefinition- 如果
id
不為空,則取其作為名稱 - 否則,生成一個名稱,也就是
className
- 如果
-
獲取這個 Advisor 對應的 Pointcut(也許就是一個 AspectJExpressionPointcut,也可能是引用的 Pointcut 的名稱)
- 如果是 AspectJExpressionPointcut,第
1
步建立的 RootBeanDefinition 新增pointcut
屬性,指向這個 AspectJExpressionPointcut - 否則,如果是一個引用的 Pointcut 的名稱,第
1
步建立的 RootBeanDefinition 新增pointcut
屬性,指向這個名稱對應的引用
- 如果是 AspectJExpressionPointcut,第
<aop:aspect />
<beans>
<aop:aspectj-autoproxy/>
<bean id="aspectXmlConfig" class="org.geekbang.thinking.in.spring.aop.features.aspect.AspectXmlConfig"/>
<aop:config>
<aop:aspect id="AspectXmlConfig" ref="aspectXmlConfig">
<aop:pointcut id="anyPublicMethod" expression="execution(public * *(..))"/>
<aop:around method="aroundAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:before method="beforeAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:before method="beforeAnyPublicMethod" pointcut="execution(public * *(..))"/>
<aop:after method="finalizeAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:after-returning method="afterAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:after-throwing method="afterThrowingAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
</aop:aspect>
</aop:config>
</beans>
處理過程在 parseAspect(..)
方法中,如下:
private void parseAspect(Element aspectElement, ParserContext parserContext) {
// <1> 獲取 `id` 和 `ref` 屬性
String aspectId = aspectElement.getAttribute(ID);
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
// <2> 定義兩個集合 `beanDefinitions`、`beanReferences`
// 解析出來的 BeanDefinition
List<BeanDefinition> beanDefinitions = new ArrayList<>();
// 需要引用的 Bean
List<BeanReference> beanReferences = new ArrayList<>();
// <3> 獲取所有的 <aop:declare-parents /> 子標籤,遍歷進行處理
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
Element declareParentsElement = declareParents.get(i);
// <3.1> 解析 <aop:declare-parents /> 子標籤
// 解析出 DeclareParentsAdvisor 物件,新增至 `beanDefinitions`
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
}
// We have to parse "advice" and all the advice kinds in one loop, to get the
// ordering semantics right.
// <4> 獲取 <aop:aspectj /> 所有的子節點,遍歷進行處理
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// <4.1> 如果是 <aop:around />、<aop:before />、<aop:after />、<aop:after-returning />、<aop:after-throwing /> 標籤,則進行處理
if (isAdviceNode(node, parserContext)) {
// <4.2> 如果第一次進來,那麼就是配置了 Advice,則 `ref` 必須指定一個 Bean,因為這些 Advice 的 `method` 需要從這個 Bean 中獲取
if (!adviceFoundAlready) {
adviceFoundAlready = true;
if (!StringUtils.hasText(aspectName)) {
parserContext.getReaderContext().error(
"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
aspectElement, this.parseState.snapshot());
return;
}
// <4.2.1> 往 `beanReferences` 新增需要引用的 Bean
beanReferences.add(new RuntimeBeanReference(aspectName));
}
// <4.3> 根據 Advice 標籤進行解析
// 建立一個 AspectJPointcutAdvisor 物件,裡面包含了 Advice 物件和對應的 Pointcut 物件,並進行註冊
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
// <4.4> 新增至 `beanDefinitions` 中
beanDefinitions.add(advisorDefinition);
}
}
// <5> 將上面建立的所有 Advisor 和引用物件都封裝到 AspectComponentDefinition 物件中
// 並放入 `parserContext` 上下文中,暫時忽略
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
// <6> 獲取所有的 <aop:pointcut /> 子標籤,進行遍歷處理
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
// <6.1> 解析出 AspectJExpressionPointcut 物件並註冊
parsePointcut(pointcutElement, parserContext);
}
parserContext.popAndRegisterContainingComponent();
} finally {
this.parseState.pop();
}
}
解析過程大致如下:
- 獲取
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 子標籤
private AbstractBeanDefinition parseAdvice(
String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
try {
this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));
// create the method factory bean
// <1> 建立 MethodLocatingFactoryBean 型別的 RootBeanDefinition
// 因為通過標籤配置的 Advice 對應的方法在其他 Bean 中,那麼可以藉助於 FactoryBean 來進行建立
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
// <1.1> 獲取 `targetBeanName` 和 `method` 並進行設定
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
// <1.2> 設定這個 Bean 是由 Spring 內部合成的
methodDefinition.setSynthetic(true);
// create instance factory definition
// <2> 建立一個 SimpleBeanFactoryAwareAspectInstanceFactory 型別的 RootBeanDefinition
RootBeanDefinition aspectFactoryDef = new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
// <2.1> 設定了 AspectJ 對應的 名稱,用於獲取這個 AspectJ 的例項物件
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
// <2.2> 設定這個 Bean 是由 Spring 內部合成的
aspectFactoryDef.setSynthetic(true);
// register the pointcut
// <3> 建立一個 Advice 物件,包含了對應的 Pointcut
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
beanDefinitions, beanReferences);
// configure the advisor
// <4> 建立一個 AspectJPointcutAdvisor 型別的 RootBeanDefinition 物件,用於包裝上面建立的 Advice
// Spring AOP 中的 Advice 都是放入 Advisor “容器” 中
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
// <4.1> 設定來源
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
// <4.2> 將上面建立的 Advice 物件作為構造器入參
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
// <4.3> 設定 `order` 優先順序
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
}
// register the final advisor
// <5> 註冊這個 AspectJPointcutAdvisor,自動生成名字
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
// <6> 返回這個已註冊的 AspectJPointcutAdvisor
return advisorDefinition;
}
finally {
this.parseState.pop();
}
}
處理過程大致如下:
-
建立 MethodLocatingFactoryBean 型別的 RootBeanDefinition,因為通過標籤配置的 Advice 對應的方法在其他 Bean 中,那麼可以藉助於 FactoryBean 來進行建立
- 獲取
targetBeanName
和method
並進行設定 - 設定這個 Bean 是由 Spring 內部合成的
- 獲取
-
建立一個 SimpleBeanFactoryAwareAspectInstanceFactory 型別的 RootBeanDefinition
- 設定了 AspectJ 對應的 名稱,用於獲取這個 AspectJ 的例項物件
- 設定這個 Bean 是由 Spring 內部合成的
-
建立一個 Advice 物件,包含了對應的 Pointcut
private AbstractBeanDefinition createAdviceDefinition( Element adviceElement, ParserContext parserContext, String aspectName, int order, RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef, List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) { // <1> 根據 Advice 標籤建立對應的 Advice // <aop:before /> -> AspectJMethodBeforeAdvice // <aop:after /> -> AspectJAfterAdvice // <aop:after-returning /> -> AspectJAfterReturningAdvice // <aop:after-throwing /> -> AspectJAfterThrowingAdvice // <aop:around /> -> AspectJAroundAdvice RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext)); // <1.1> 設定來源 adviceDefinition.setSource(parserContext.extractSource(adviceElement)); // <1.2> 設定引用的 AspectJ 的名稱 adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName); // <1.3> 設定優先順序 adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order); if (adviceElement.hasAttribute(RETURNING)) { adviceDefinition.getPropertyValues().add( RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING)); } if (adviceElement.hasAttribute(THROWING)) { adviceDefinition.getPropertyValues().add( THROWING_PROPERTY, adviceElement.getAttribute(THROWING)); } if (adviceElement.hasAttribute(ARG_NAMES)) { adviceDefinition.getPropertyValues().add( ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES)); } // <2> 獲取 Advice 的構造器引數物件 `cav` // 設定 1. 引用的方法、2. Pointcut(也許是引用的 Pointcut 的名稱)、3. 引用的方法所屬 AspectJ 物件 // 你點進這些 Advice 型別的物件中看看構造方法就知道怎麼回事,例如:AspectJMethodBeforeAdvice ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues(); // <2.1> 往 `cav` 新增 Advice 對應的方法作為入參 cav.addIndexedArgumentValue(METHOD_INDEX, methodDef); // <2.2> 解析出對應的 Pointcut 物件(可能是一個 AspectJExpressionPointcut,也可能是引用的 Pointcut 的一個執行時引用物件) Object pointcut = parsePointcutProperty(adviceElement, parserContext); // <2.2.1> 如果是 AspectJExpressionPointcut if (pointcut instanceof BeanDefinition) { // 往 `cav` 新增 `pointcut` 入參 cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut); // 新增至 `beanDefinitions` beanDefinitions.add((BeanDefinition) pointcut); } // <2.2.2> 否則,如果是 引用的 Pointcut else if (pointcut instanceof String) { // 根據引用的 Pointcut 的名稱生成一個引用物件 RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut); // 往構 `cav` 新增 `pointcut` 入參 cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef); // 新增至 `pointcutRef` beanReferences.add(pointcutRef); } // <2.3> 往 `cav` 新增 Advice 對應的方法所在 Bean 作為入參 cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef); // <3> 返回為 Advice 建立的 RootBeanDefinition 物件 return adviceDefinition; } private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) { String elementName = parserContext.getDelegate().getLocalName(adviceElement); if (BEFORE.equals(elementName)) { return AspectJMethodBeforeAdvice.class; } else if (AFTER.equals(elementName)) { return AspectJAfterAdvice.class; } else if (AFTER_RETURNING_ELEMENT.equals(elementName)) { return AspectJAfterReturningAdvice.class; } else if (AFTER_THROWING_ELEMENT.equals(elementName)) { return AspectJAfterThrowingAdvice.class; } else if (AROUND.equals(elementName)) { return AspectJAroundAdvice.class; } else { throw new IllegalArgumentException("Unknown advice kind [" + elementName + "]."); } }
-
建立一個 AspectJPointcutAdvisor 型別的 RootBeanDefinition 物件,用於包裝上面建立的 Advice,Spring AOP 中的 Advice 都是放入 Advisor “容器” 中
-
註冊這個 AspectJPointcutAdvisor,自動生成名字
-
返回這個已註冊的 AspectJPointcutAdvisor
------------------------------------
Spring Boot 註解驅動
在 Spring Boot 中使用 Spring AOP 我們通常會這樣進行 Maven 引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
上面這個依賴內部會引入這樣兩個依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.10.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>compile</scope>
</dependency>
引入相關依賴後,同樣可以使用 @EnableAspectJAutoProxy
註解來驅動整個 Spring AOP 依賴,不過在 Spring AOP 中你不需要顯示地使用這個註解,因為在 spring-boot-autoconfigure
中,有一個 AOP 自動配置類,我們一起來看看
AopAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
,Spring Boot 中的 AOP 自動配置類
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
可以看到這個 @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 模組。