寫在前面
- 具備Spring IOC以及AOP源碼分析基礎 <單擊一鍵,原理盡顯>
- Advisor
它包含AOP 通知(在連接點採取的操作)和確定建議適用性的過濾器(例如切入點),簡單的說的來說可以理解成攔截器或者切面。 - @DeclareParents
用來標註對象屬性,可以爲對象添加一個新方法。 - InterceptorAndDynamicMethodMatcher
動態攔截器,根據運行時參數來決定攔截器是否生效,有動態攔截器當然也有靜態攔截器,靜態攔截器一般通過包名,類名,方法名 ,參數來確定靜態攔截器是否對代理方法生效。 - 本文調試代碼獲取
- 閱讀本文你將收穫:
- AOP切面配置如何被加載的?
- PartialOrder究竟對Advice(MethodIntercept)做出了何種排序?
- AOP如何通過遞歸實現的調用鏈設計模式?
- 事務爲何會失效?
前情回顧
AOP的一切要從 @EnableAspectJAutoProxy說起。
它通過 @Import註解AspectJAutoProxyRegistrar類,這個類是ImportBeanDefinitionRegistrar實現類。
通過這種手動注入方式,最終將AnnotationAwareAspectJAutoProxyCreator注入到IOC容器中。
查看AnnotationAwareAspectJAutoProxyCreator的類圖,可以發現它是BeanPostProcessor的實現,BeanPostProcessor允許對Bean進行增強操作。
增強操作分爲兩種:前置(postProcessBeforeInitialization)、後置(postProcessAfterInitialization)增強,最終我們在AnnotationAwareAspectJAutoProxyCreator的其中一個超類中找到了這兩個增強方法的實現,那就是AbstractAutoProxyCreator。
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
最終,我們發現AbstractAutoProxyCreator在前置增強中並未做任何處理,而我們的故事也就是從後置增強中開始……
咳咳~
好啦,故事我們講完了,現在開始源碼分析。
源碼分析
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 省略部分代碼……
// 1、獲取所有Advisors(可以理解成攔截器或者切面),官方定義https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/Advisor.html
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 2、創建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
我將整個過程分兩步:
1、切入點的解析與Advice初排序
2、代理中的遞歸調用鏈邏輯分析
它們分別隸屬getAdvicesAndAdvisorsForBean()分支以及createProxy()分支,那麼我們先來看第一部分的源碼分析。
切入點的解析與Advice初排序
切入點加載的調用鏈源碼解析
IDEA Ctrl+Alt+B 可以發現,getAdvicesAndAdvisorsForBean有兩種實現,當方法是具有多個實現的抽象方法時,可以選擇調試的方式確定最終的調用方向。
最終,我們可以確定代碼走向AbstractAdvisorAutoProxyCreator的實現。
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
// 尋找合格的顧問
——> protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 查詢所有候選Advisors
// Ctrl+Alt+B 進入AnnotationAwareAspectJAutoProxyCreator的findCandidateAdvisors()實現
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
——> protected List<Advisor> findCandidateAdvisors() {
// 執行父類中的findCandidateAdvisors()方法,也就是我們上一段代碼所在類。
List<Advisor> advisors = super.findCandidateAdvisors();
// 查詢添加Bean工廠中所有的AspectJ切面Advisor。
if (this.aspectJAdvisorsBuilder != null) {
// 查詢添加Bean工廠中所有的AspectJ切面Advisor,被@AspectJ標註的類將被IOC做特殊標記
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
到這裏我們可以初略的將Advisor分爲兩類,過濾器以及切面,接下來我們重點關注一下切面(@AspectJ標註的類)如何被解析的?以及如何進行的排序?
——> public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new LinkedList<>();
aspectNames = new LinkedList<>();
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
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);
if (beanType == null) {
continue;
}
// 1、過濾被@AspectJ標註的Bean
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
// 2、獲取當前切面下的所有Advisor切入點
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);
}
else {
// 省略部分代碼……
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
// 省略部分尾部代碼……
return advisors;
}
Spring 如何獲取切入點信息?初排序Order如何設置?
——>public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
// 驗證當前實例,驗證@AspectJ標註等
validate(aspectClass);
// 延遲加載相關.
MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
List<Advisor> advisors = new LinkedList<>();
for (Method method : getAdvisorMethods(aspectClass)) {
// 我們重點關注這一行,可以看到的是
// declarationOrderInAspect參數入參爲 advisors.size(),這意味着
// 這與Advisor之後的Order排序有着密切的關係,從賦值來看,順序爲Advisor聲明的順序
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), 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標註的屬性.
for (Field field : aspectClass.getDeclaredFields()) {
Advisor advisor = getDeclareParentsAdvisor(field);
if (advisor != null) {
advisors.add(advisor);
}
}
return advisors;
}
@DeclareParents用來標註對象屬性,可以爲對象添加一個新方法。繼續追蹤調用鏈,查看如何創建的切入點Advisor:
——> public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
// 1、超類@AspectJ標註等驗證
validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
// 2、獲取切入點配置信息。說白了,就是在查找方法上是否有AOP切入點相關的註解標註
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
// 在這裏將過濾掉所有沒有切入點配置的方法
if (expressionPointcut == null) {
return null;
}
// 3、構造切入點Advisor,關注到declarationOrderInAspect的賦值
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
// 看看Spring是如何獲取到方法上面的切入點的
——> private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
// 查找方法是否有被@AspectJ類註解標註
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;
}
——> protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
// 看到熟悉的AOP切入點註解:@Before、@Around、@After、@AfterReturning、@AfterThrowing、@Pointcut
Class<?>[] classesToLookFor = new Class<?>[] {
Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
// 若方法被其中某個標註,則爲切入點
for (Class<?> c : classesToLookFor) {
AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
if (foundAnnotation != null) {
return foundAnnotation;
}
}
// 非切入點方法,則會被過濾掉
return null;
}
最終,根據AOP的切入點註解過濾出來的切面方法,將被構建成,InstantiationModelAwarePointcutAdvisorImpl。
查看它的類圖可以發現其實現了Order接口,我們知道Order接口是Spring排序用的,這在我們講解IOC時提到過,接下來我們就看一下,Spring是如何對這些切入點進行排序的?這種排序可以保證方法最終的執行順序嗎?
Advice初排序的調用鏈源碼解析
回到我們尋找所有Advice代碼處findEligibleAdvisors,接着進行初排序分析。
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
AnnotationAwareOrderComparator.sort(advisors);
return advisors;
}
// AspectJAwareAdvisorAutoProxyCreator類中的實現
——> protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors =
new ArrayList<>(advisors.size());
for (Advisor element : advisors) {
partiallyComparableAdvisors.add(
new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
}
// 根據Order值進行排序,可通過@Order進行設置,order的值越小其優先級越高
List<PartiallyComparableAdvisorHolder> sorted =
PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
List<Advisor> result = new ArrayList<>(advisors.size());
for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
}
return result;
}
else {
return super.sortAdvisors(advisors);
}
}
可以看到,最終Order排序與切入點方法的執行順序並不相同,那麼Spring 又是如何保證切入點方法與代理方法的執行順序的呢,也就是誰前誰後?
遞歸實現的調用鏈源碼分析
通過以上代碼分析,我們瞭解到我們可以通過@Order註解標註切面的執行順序,order的值越小其優先級越高。
Advisor如何層層傳遞調用的?這一切都依仗於責任鏈設計模式。
createProxy()經過層層調用將根據Bean實例是否是接口實現類判斷採用JDK或CgLib動態代理,被代理的對象實例的方法在執行時,將會執行invoke()或intercept()方法。(如有疑問,請先了解AOP原理)
現在,我們就從invoke()/intercept()方法開始分析,調用鏈究竟如何實現的。
不論是採用JDK或是CgLib,你會發現在它們各自的代理方法中都包括如下這段代碼,而這就是AOP Advisor 調用鏈的入口。
// 獲取當前代理的方法的所有Advice。
// 如果你細細研究,會發現在getInterceptorsAndDynamicInterceptionAdvice中,有一段代碼,
// 將所有的AOP切面Advice轉換成了MethodInterceptor接口。
// MethodInterceptor則是Spring所有切入點方法的祖先。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 通過反射技術創建調用鏈執行方法...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 現在執行所有的攔截器和切面Advice.
retVal = invocation.proceed();
}
——> public Object proceed() throws Throwable {
// 執行完所有Advice後執行代理方法,currentInterceptorIndex 是標識調用鏈位置的偏移量。
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 獲取下一個要執行的Advice
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 執行動態方法匹配器
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
// InterceptorAndDynamicMethodMatcher 爲動態攔截器,根據運行時參數來決定攔截器是否生效,有動態攔截器當然也有靜態攔截器,靜態攔截器一般通過包名,類名,方法名 ,參數來確定靜態攔截器是否對代理方法生效
return dm.interceptor.invoke(this);
}
else {
// 動態方法匹配失敗,跳過此攔截器並調用鏈中的下一個Advice攔截器,遞歸執行。
return proceed();
}
}
else {
// 執行靜態攔截器,我們的AOP 切入點都是此類攔截器(包括@DeclareParents)。
// 這裏將this傳入過去,就是爲了實現遞歸責任鏈
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
Ctrl+Alt+B 可以看到invoke(this)有非常多的實現,我們重點關注@Before、@Around、@After、@AfterReturning、@AfterThrowing、@Pointcut的實現。
首先,我們需要了解它們各自的執行時機:
@Before:前置通知,在方法執行之前運行;
@After:後置通知,在方法返回結果之後運行;
@AfterReturning:返回結果通知,方法返回結果之後運行,也攔截返回的結果;
@Around:環繞通知,運行方法執行;
@AfterThrowing:異常通知,在方法引發異常之後運行。
// MethodBeforeAdviceInterceptor 前置通知
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}
// AspectJAfterAdvice 後置通知
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
// AfterReturningAdviceInterceptor 返回結果通知
public Object invoke(MethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
// AspectJAroundAdvice 環繞通知
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
// 具體實現方式參閱invokeAdviceMethod部分代碼。
return invokeAdviceMethod(pjp, jpm, null, null);
}
// AspectJAfterThrowingAdvice 異常通知
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
if (shouldInvokeOnThrowing(ex)) {
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
最終,Spring通過層層傳遞的this進行遞歸調用proceed(),通過currentInterceptorIndex偏移量記錄調用鏈執行位置,通過反射調用目標方法,進而實現層層傳遞,層層調用的責任鏈模型。
AOP 調用鏈總結
1、第一步,Spring IOC Bean後置處理器執行階段,解析被@AspectJ註解標註的實例;
2、第二步,解析@AspectJ標註的實例,這個過程是解析器內部的切入點、以及切面方法,最終將解析成MethodInterceptor接口1(當然這個過程還包括Spring其他攔截器的解析)。主要解析@Before、@Around、@After、@AfterReturning、@AfterThrowing、@Pointcut註解;
3、第三步,根據order值,對MethodInterceptor排序,order的值越小其優先級越高。MethodInterceptor的order值等於它們的聲明(加載)順序,當然我們也可以通過@Order註解標註他們的順序,Order的大小決定它們最終的執行順序;
4、第四步,當被代理類方法執行前,調用攔截鏈邏輯,主要依據下圖所示實現。
4.1、使用一個列表(interceptorsAndDynamicMethodMatchers)存放排序後的MethodInterceptor;
4.2、使用一個偏移量(currentInterceptorIndex)標識當前調用鏈執行下標;
4.3、遞歸過程中傳遞this關鍵字保證以上兩個變量的全局一致;
4.4、切入點邏輯依照不同類型的MethodInterceptor而選擇。例如,前置通知,在方法之前執行等;
4.5、層層傳遞,責任調用。前一個切面代碼執行完交由下一個切面執行,形成鏈狀模型。
事務爲何會失效?
public class TestTransactional {
@Transactional(propagation = Propagation.REQUIRED)
public void A() {
User user = new User("chunsoft");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("拋異常");
}
}
public void B() {
this.A();
}
}
參考實例給出的代碼,這裏的事務爲何會失效呢?我們先查看一下事務的執行代碼,它也是MethodInterceptor的一種實現,上面我們介紹的AOP切面執行邏輯是一樣的,它的名字叫做TransactionInterceptor。
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 執行事務邏輯...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 如果transaction屬性爲null,則該方法爲非事務處理。.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 創建事務管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 開啓事務
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 調用鏈中的下一個攔截器.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 事務回滾
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 重置TransactionInfo ThreadLocal
cleanupTransactionInfo(txInfo);
}
// 提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// 省略CallbackPreferringPlatformTransactionManager部分代碼……
}
}
可以看到,最終事務通過反射進行調用,但若@Transactional標註的方法在同類內被調用,則事務不會生效,因爲Jdk/CgLib代理是基於對象的,需要在解析切入點是解析@Transactional註解的,同類調用,則代理不生效,事務不生效。
這裏之所以說解析成MethodInterceptor而不是Advice,是因爲MethodInterceptor繼承了Advice,便於我們理解記憶。 ↩︎