該系列文章是本人在學習 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 對象,則調用其
方法擷取需要導入的 Bean 的名稱String[] selectImports(AnnotationMetadata)
- 否則,如果是一個 ImportBeanDefinitionRegistrar 對象,先儲存起來,在後面調用其
方法,支援注冊相關 BeanregisterBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)
- 否則,會注冊這個 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
,則進行設定(開啟類代理,也就是開啟 CGLIB 動态代理)true
- 如果
為exposeProxy
,則進行設定(需要暴露代理對象,也就是在 Advice 或者被攔截的方法中可以通過 AopContext 擷取代理對象)true
- 如果
可以看到會注冊一個
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 對象
- 如果已注冊的
和入參的 Class 不相等,說明可能是繼承關系internalAutoProxyCreator
- 擷取已注冊的
的優先級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
中注冊幾個标簽解析器或者裝飾器:
-
:ConfigBeanDefinitionParser<aop:config />
-
:AspectJAutoProxyBeanDefinitionParser<aop:aspectj-autoproxy />
-
:ScopedProxyBeanDefinitionDecorator<aop:scoped-proxy />
繼續看到 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;
}
該方法的處理過程如下:
- 解析
标簽,注冊 AspectJAwareAdvisorAutoProxyCreator 自動代理對象(如果需要的話),設定為優先級最高<aop:config />
過程和private void configureAutoProxyCreator(ParserContext parserContext, Element element) { // 注冊 AspectJAwareAdvisorAutoProxyCreator 自動代理對象(如果需要的話) AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element); }
、@EnableAspectJAutoProxy
的解析過程差不多,這裡不再進行展述<aop:aspectj-autoproxy />
- 擷取
的子标簽,周遊進行處理<aop:config />
- 調用
方法,處理parsePointcut(..)
子标簽,解析出 AspectJExpressionPointcut 對象并注冊<aop:pointcut />
- 調用
方法,處理parseAdvisor(..)
子标簽,解析出 DefaultBeanFactoryPointcutAdvisor 對象并注冊,了指定 Advice 和 Pointcut(如果有)<aop:advisor />
- 調用
方法,處理parseAspect(..)
子标簽,解析出所有的 AspectJPointcutAdvisor 對象并注冊,裡面包含了 Advice 對象和對應的 Pointcut 對象;同時存在 Pointcut 配置,也會解析出 AspectJExpressionPointcut 對象并注冊<aop:aspectj />
- 調用
我們依次來看看你上面三種子标簽的處理過程
<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
- 根據
表達式建立一個 AspectJExpressionPointcut 類型的 RootBeanDefinition 對象,如下:expression
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();
}
}
解析過程大緻如下:
- 解析
标簽,建立一個 DefaultBeanFactoryPointcutAdvisor 類型的 RootBeanDefinition 對象,并指定了 Advice<aop:advisor />
private 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
- 注冊第
步建立的 RootBeanDefinition1
- 如果
不為空,則取其作為名稱id
- 否則,生成一個名稱,也就是
className
- 如果
- 擷取這個 Advisor 對應的 Pointcut(也許就是一個 AspectJExpressionPointcut,也可能是引用的 Pointcut 的名稱)
- 如果是 AspectJExpressionPointcut,第
步建立的 RootBeanDefinition 添加1
屬性,指向這個 AspectJExpressionPointcutpointcut
- 否則,如果是一個引用的 Pointcut 的名稱,第
步建立的 RootBeanDefinition 添加1
屬性,指向這個名稱對應的引用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
,分别儲存解析出來的 BeanDefinition 和需要引用的 BeanbeanReferences
- 擷取所有的
子标簽,周遊進行處理<aop:declare-parents />
- 解析
子标簽,解析出 DeclareParentsAdvisor 對象并注冊,添加至<aop:declare-parents />
beanDefinitions
- 解析
- 擷取
所有的子節點,周遊進行處理<aop:aspectj />
- 如果是
标簽,則進行處理<aop:around />、<aop:before />、<aop:after />、<aop:after-returning />、<aop:after-throwing />
- 如果第一次進來,那麼就是配置了 Advice,則
必須指定一個 Bean,因為這些 Advice 的ref
需要從這個 Bean 中擷取method
- 往
添加需要引用的 BeanbeanReferences
- 往
- 根據 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
三個 Class 對象也就存在了,而AnnotatedElement
這個注解本身就存在 Spring 中,是以這個注解是滿足條件的EnableAspectJAutoProxy
-
注解:指定的配置為@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 子產品。