概述
在之前的文章中,我們已經對 SpringAOP 的運行機制有了清晰的瞭解。然而,在本文中我們將補充關於 AspectJ
切面的內容。
儘管我們可以使用 AspectJ
的註解來定義切面邏輯,但實際上它們的實現仍然基於 Advisor
和方法攔截器。我們可以在最常用的 AutoProxyCreator
,也就是 AnnotationAwareAspectJAutoProxyCreator
中找到相關證據。
AnnotationAwareAspectJAutoProxyCreator
是 AutoProxyCreator
的最底層實現類。與另一個底層實現類 BeanNameAutoProxyCreator
相比,它主要區別在於對 AspectJ
的支持。以下是該類的部分源碼:
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
// 正則表達式匹配器
@Nullable
private List<Pattern> includePatterns;
// AspectJ 專用的通知器工廠
@Nullable
private AspectJAdvisorFactory aspectJAdvisorFactory;
// AspectJ 專用的通知類構建器
@Nullable
private BeanFactoryAspectJAdvisorsBuilder aspectJAdvisorsBuilder;
}
接下來,我們將以這個類爲基礎展開討論。
通過閱讀本文,我們將深入瞭解 AnnotationAwareAspectJAutoProxyCreator
類的實現細節,包括它與 AspectJ
的集成和專用通知器工廠的使用。這將幫助我們更好地理解 SpringAOP 中 AspectJ
的支持,爲我們的 AOP 應用提供更多選擇和功能。
本專題共三篇文章,這是第三篇:
- 深入理解 SpringAOP(一):AOP 組件概述
- 深入理解 SpringAOP(二):AOP的執行流程;
- 深入理解 SpringAOP(三):AspectJ支持;
一、AspectJ 通知器的獲取
對於 AnnotationAwareAspectJAutoProxyCreator
,我們只需要關注最核心的 findCandidateAdvisors
方法,它重寫自 AbstractAdvisorAutoProxyCreator
,在父類中,這一步用來找到 spring 容器中所有可用的 Advisor
,而它在父類的基礎上,又額外扔進去了用於支持 AspectJ
的額外通知器:
@Override
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
// 通過 AspectJ 通知構建器創建一批通知器
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
然後是 buildAspectJAdvisors
方法:
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
// 沒有任何已加載的 AspectJ 通知類,通過雙重檢查進行初始化
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
// 獲取並遍歷所有的beanName
for (String beanName : beanNames) {
if (!isEligibleBean(beanName)) {
continue;
}
// We must be careful not to instantiate beans eagerly as in this case they
// would be cached by the Spring container but would not have been weaved.
Class<?> beanType = this.beanFactory.getType(beanName, false);
if (beanType == null) {
continue;
}
// 檢查這個 bean 的類上是否有 @Aspect 註解且不由 AspectJ 編譯的類
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
// 獲取這個切面類的元數據,即類型和各種註解
AspectMetadata amd = new AspectMetadata(beanType, beanName);
// 1、如果切面類是單例的
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
// 通過切面工廠爲其創建單例的通知器
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
// 2、如果切面類是非單例的
else {
// 被代理的bean必須也是非單例的
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
// 爲其緩存切面工廠,每次都爲 bean 創建一個新的切面工廠
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
// 如果已經初始化過切面緩存,則從緩存中:
// 1、如果 bean 是單例的,那麼直接從緩存獲取通知器;
// 2、如果 bean 是非單例的,那麼先從緩存獲取切面工廠,再創建通知器
if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
List<Advisor> advisors = new ArrayList<>();
for (String aspectName : aspectNames) {
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}
這邊主要做了這麼些工作:
- 從 Spring 容器中獲得所有類上帶有
@Aspect
註解且類不由AspectJ
編譯 bean,這些 bean 就是切面對象; - 檢查切面類是否是單例的:
- 如果是切面類是單例的,那麼就創建切面/通知器工廠
MetadataAwareAspectInstanceFactory
,根據其生成一批通知器。假如要代理的bean
,那就直接將通知器們註冊到緩存,否則就直接緩存該工廠; - 如果切面類不是單例的,要代理的
bean
也不能是單例的。爲其創建工廠後,直接緩存該工廠;
- 如果是切面類是單例的,那麼就創建切面/通知器工廠
- 上述獲取切面,生成並緩存通知器工廠、通知器的操作僅進行一次,後續操作將直接進行後續步驟;
- 當根據 bean 獲取通知器的時候,先嚐試直接從
advisorsCache
獲取通知器緩存,如果沒有再從獲取aspectFactoryCache
獲取工廠,然後再創建通知器;
二、切面工廠
在上述代碼中,在爲切面創建了 MetadataAwareAspectInstanceFactory
後,通過 AspectJ
通知器工廠 AspectJAdvisorFactory
真正的創建了通知器。我們暫不關注 MetadataAwareAspectInstanceFactory
的實現邏輯,而關注從通知器工廠獲取通知器的方法 getAdvisors
:
// 來自 ReflectiveAspectJAdvisorFactory,其爲 AspectJAdvisorFactory 唯一一個實現類
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
validate(aspectClass);
// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
// so that it will only instantiate once.
MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
List<Advisor> advisors = new ArrayList<>();
// 獲取切面類中所有不被 @Pointcut 註解、由用戶在類中直接聲明方法
for (Method method : getAdvisorMethods(aspectClass)) {
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
// If it's a per target aspect, emit the dummy instantiating aspect.
if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
advisors.add(0, instantiationAdvisor);
}
// 掃描帶有 @DeclareParents 註解的屬性,並根據註解生成 DeclareParentsAdvisor
// Find introduction fields.
for (Field field : aspectClass.getDeclaredFields()) {
Advisor advisor = getDeclareParentsAdvisor(field);
if (advisor != null) {
advisors.add(advisor);
}
}
return advisors;
}
@Override
@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
// MethodFilter adviceMethodFilter = ReflectionUtils.USER_DECLARED_METHODS
// .and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null));
List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);
if (methods.size() > 1) {
methods.sort(adviceMethodComparator);
}
return methods;
}
這個方法主要幹兩件事:
- 遍歷所有直接在類中聲明的、不帶有
@Pointcut
註解的切點方法,調用getAdvisor
方法將其適配爲通知器; - 遍歷所有直接在切面聲明的,帶有
@DeclareParents
註解的接口引入屬性,將其適配爲DeclareParentsAdvisor
;
三、適配切點方法
我們關注一下 getAdvisor
方法,我們熟悉的那些切點方法,比如 @Around
, @Before
, @After
, @AfterReturning
, @AfterThrowing
都在這裏解析爲通知器:
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
// 獲取方法上的 AspectJ 註解,比如 @Around, @Before, @After, @AfterReturning, @AfterThrowing
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
AspectJExpressionPointcut ajexp =
new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
if (this.beanFactory != null) {
ajexp.setBeanFactory(this.beanFactory);
}
return ajexp;
}
這裏主要是兩步邏輯:
- 獲取切點,即
getPointcut
方法,裏面的邏輯就不細究了,大體就是創建一個AspectJExpressionPointcut
,它支持根據AspectJ
的各種配置去匹配類型或者方法; - 創建切面,即創建一個
InstantiationModelAwarePointcutAdvisorImpl
,它就是最終的 AspectJ 通知器;
當調用 InstantiationModelAwarePointcutAdvisorImpl
的 getAdvice
方法的時候,會根據傳入的 AspectJAspectFactory
(一般是 ReflectiveAspectJAdvisorFactory
)去基於切點方法創建具體的 MethodMethodInterceptor
;
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
validate(candidateAspectClass);
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
// If we get here, we know we have an AspectJ method.
// Check that it's an AspectJ-annotated class
if (!isAspect(candidateAspectClass)) {
throw new AopConfigException("Advice must be declared inside an aspect type: " +
"Offending method '" + candidateAdviceMethod + "' in class [" +
candidateAspectClass.getName() + "]");
}
if (logger.isDebugEnabled()) {
logger.debug("Found AspectJ method: " + candidateAdviceMethod);
}
AbstractAspectJAdvice springAdvice;
// 根據方法的類型,創建不同的切面類
switch (aspectJAnnotation.getAnnotationType()) {
case AtPointcut -> {
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
}
case AtAround -> springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
case AtBefore -> springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
case AtAfter -> springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
case AtAfterReturning -> {
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
}
case AtAfterThrowing -> {
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
}
default -> throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}
// Now to configure the advice...
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrder);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if (argNames != null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
springAdvice.calculateArgumentBindings();
return springAdvice;
}
這個方法具體的實現不必深究,我們知道它是怎麼回事即可。
四、適配接口引入
上面我們提到了 @DeclareParents
註解,它是用來做接口引入的。其實這是個很強大但是也挺冷門的功能。
首先,在上文我們會注意到,Advisor
還有一個分支,叫做 IntroductionAdvisor
, AspectJ
和 Spring 都支持接口引入(Introduction
),也就是讓一個類憑空實現額外的接口,從而具備這些可調用的方法,和 kotlin 的擴展函數有點像。
舉個例子:
@Aspect
public class GreetingAspect {
// 使 com.example.Person 這個類實現 Foo 接口,並將方法委託到當前屬性對應的 Foo 上
@DeclareParents(value = "com.example.Person", defaultImpl = FooImpl.class)
private Foo foo;
}
在上述例子中,我們爲 com.example.Person
這個類的代理類引入了 Foo
接口,並且會通過反射爲其創建一個 FooImpl
實例,當我們通過 Person
的代理對象調用 Foo
接口中的方法時,就會委託到 FooImpl
實例上。
不過由於在編譯期無法顯式的調用這個方法,且完全依賴 Spring 代理,因此這個功能的存在感並不高。
總結
用於支持 AspectJ
切面的 AnnotationAwareAspectJAutoProxyCreator
重寫了父類 findCandidateAdvisors
的方法,並通過 buildAspectJAdvisors
解析並獲取了所有基於 AspectJ 實現的通知器並將其添加進了通知器列表,在這個過程中:
- 從 Spring 容器中獲得所有類上帶有
@Aspect
註解且類不由AspectJ
編譯 bean,這些 bean 就是切面對象; - 爲這些類創建對應的切面工廠
MetadataAwareAspectInstanceFactory
,這些工廠將用於創建對應的 AspectJ 通知器; - 當通過切面工廠的
getAdvisors
方法獲得 AspectJ 通知器時,將會:- 遍歷所有直接在切面類中聲明的、不帶有
@Pointcut
註解的切點方法,調用getAdvisor
方法將其適配爲通知器:@Around
,@Before
,@After
,@AfterReturning
,@AfterThrowing
註解將會被解析爲相應的 AspectJ 表達式切點AspectJExpressionPointcut
;- 獲得切點後,將會創建一個 AspectJ 通知器
InstantiationModelAwarePointcutAdvisorImpl
;
- 遍歷所有直接在切面聲明的,帶有
@DeclareParents
註解的接口引入屬性,將其適配爲DeclareParentsAdvisor
;
- 遍歷所有直接在切面類中聲明的、不帶有
其中,當我們通過 AspectJ 通知器 InstantiationModelAwarePointcutAdvisorImpl
的 getAdvice
獲得 Advice
時,將會:
- 通過內部持有的
AspectJAspectFactory
(一般爲ReflectiveAspectJAdvisorFactory
)創建方法攔截器MethodMethodInterceptor
; - 不同類型的 AspectJ 增強方法將會被解析爲對應的
AbstractAspectJAdvice
實現,比如AspectJAroundAdvice
或AspectJAfterAdvice
等等;