javascript
Spring AOP源码解析(一)——核心概念
目錄
Pointcut
ClassFilter
MethodMatcher?
Advisor
?IntroductionAdvisor?
PointcutAdvisor?
AbstractPointcutAdvisor
Advice??
AspectJ Advice
AbstractAspectJAdvice
Interceptor
AdvisorAdapter
TargetSource
在了解Spring事務源碼時,根據調用跟蹤,最后發現是TransactionAttributeSourcePointcut判斷是否應用了@Transactional注解,來進行AOP。TransactionAttributeSourcePointcut是Pointcut接口的一個實現。
Pointcut
Pointcut的唯一作用就是篩選要攔截的目標方法,Pointcut只是一種篩選規則(或者叫過濾器)。
Pointcut由ClassFilter(類過濾器)和MethodMatcher(方法匹配器)兩個組件組成。ClassFilter檢查當前篩選規則與目標類是否匹配,MethodMatcher檢查當前篩選規則與目標方法是否匹配。兩個組件的共同作用,可以篩選出一個符合既定規則的方法的集合。通過Advisor(通知器)和Advice(通知)和Pointcut(切點)組合起來,就可以把指定的通知應用到指定的方法集合上。
????? ? Pointcut定義源碼如下:
public interface Pointcut {/*** 返回當前切點持有的類過濾器,不能返回null值。* @return*/ClassFilter getClassFilter();/*** 返回當前切點持有的方法匹配器,不能返回null值* @return*/MethodMatcher getMethodMatcher();/*** 一個最簡單切點的實現。* 這個切點由ClassFilter.TRUE和MethodMatcher.TRUE兩個組件組成* 它匹配所有的目標類和目標方法。*/Pointcut TRUE = TruePointcut.INSTANCE;}Spring Framework實現了以下PointCut。
其中比較重要的有:
- AnnotationMatchingPointcut:
注解匹配切點。根據類上或方法上是否存在指定的注解判斷切點的匹配性,如果沒有顯示指定注解,則匹配所有。
- DynamicMethodMatcherPointcut:
動態方法匹配器切點。它本質上是一個方法匹配器,但同時具有了切點的功能。
- ComposablePointcut:
可組合的切點。這種切點可以與或邏輯,任意組合其他的Pointcut、ClassFilter和MethodMatcher。其本質是通過ClassFilters和MethodMatchers兩個工具類進行Pointcut內部組件的組合。
- JdkRegexpMethodPointcut:
JDK正則表達式切點,即使用正則表達式描述方法的攔截規則和排除規則。
- StaticMethodMatcherPointcut
靜態方法匹配器切點。包括TransactionAttributeSourcePointcut(事務相關)
- AspectJExpressionPointcut:
AspectJ切點表達式切點,ExpressionPointcut的一個具體實現。顧名思義,使用AspectJ的切點表達式描述篩選規則。表達式基本語法如下(非完整語法):
execution(<方法修飾符>? <方法返回值類型> <包名>.<類名>.<方法名>(<參數類型>) [throws <異常類型>]?)ClassFilter
?ClassFilter,用于約束一個Advisor,與指定的targetClass是否匹配。只有匹配的前提下,Advisor才能使用其內部持有的Advice對targetClass進行增強。
@FunctionalInterface public interface ClassFilter {/** * 判斷指定接口或類是否匹配*/boolean matches(Class<?> clazz);ClassFilter TRUE = TrueClassFilter.INSTANCE;}?
ClassFilter有4中簡單方式的實現:
????? ? 除此之外,還有兩種特殊方式的實現:
MethodMatcher?
方法匹配器。判斷目標類的方法是否匹配。
兩個參數的matches()被稱為靜態匹配,在匹配條件不是太嚴格時使用,可以滿足大部分場景的使用,稱之為靜態的主要是區分為三個參數的matches()方法需要在運行時動態的對參數的類型進行匹配;兩個方法的分界線就是boolean isRuntime()方法,進行匹配時先用兩個參數的matches()方法進行匹配,若匹配成功,則檢查boolean isRuntime()的返回值,若為true,則調用三個參數的matches()方法進行匹配(若兩個參數的都匹配不中,三個參數的必定匹配不中)
public interface MethodMatcher {/**靜態檢查目標類的方法是否匹配。*/boolean matches(Method method, Class<?> targetClass);/*** 此MethodMatcher 是否動態的。*/boolean isRuntime();boolean matches(Method method, Class<?> targetClass, Object... args);/*** Canonical instance that matches all methods.*/MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; }?MethodMatcher 的幾種主要實現:
- DynamicMethodMatcher:動態方法匹配器。isRuntime()一直返回true。
- StaticMethodMatcher:靜態方法匹配器。isRuntime()一直返回false。
- IntersectionMethodMatcher:And 匹配器,2個匹配器都匹配才匹配。
- UnionMethodMatcher:Or匹配器,1個匹配器都匹配就匹配。
- IntroductionAwareMethodMatcher:引介匹配器。
Advisor
把Advice(通知)應用到目標類。
public interface Advisor {Advice EMPTY_ADVICE = new Advice() {};Advice getAdvice();/**是與特定的實例關聯還是所有實例共享。一般返回true,關聯指定實例。*/boolean isPerInstance();}Advisor分兩大類:IntroductionAdvisor(引介通知器)和PointcutAdvisor(切點通知器)。兩類Advisor都是為了增強targetClass,但是作用不一樣。IntroductionAdvisor主要為了給targetClass追加接口(或者說追加更多的方法),這種增強屬于類級別的增強;而PointcutAdvisor主要為了攔截方法,這種增強屬于方法級別的增強。
????? ? 正是由于兩類Advisor的增強級別不同,而導致了對ClassFilter的使用方式不同。IntroductionAdvisor進行類級別增強,因此只需要直接持有ClassFilter即可;而PointcutAdvisor進行方法級別增強,因此需要同時使用ClassFilter和MethodMatcher(方法匹配器)。PointcutAdvisor內部持有一個Pointcut,而Pointcut就是由ClassFilter和MethodMatcher組成的。
?IntroductionAdvisor?
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {ClassFilter getClassFilter();void validateInterfaces() throws IllegalArgumentException;}PointcutAdvisor?
public interface PointcutAdvisor extends Advisor {Pointcut getPointcut();}AbstractPointcutAdvisor
AbstractPointcutAdvisor繼承Ordered接口,增加了Advice(實現了Ordered)排序功能。
public int getOrder() {if (this.order != null) {return this.order;}Advice advice = getAdvice();if (advice instanceof Ordered) {return ((Ordered) advice).getOrder();}return Ordered.LOWEST_PRECEDENCE;}Advice??
AOP的目的在于對目標類或目標方法的邏輯增強(如:日志邏輯、統計邏輯、訪問控制邏輯等),那么Advice就代表要增強的具體邏輯。Advice接口由AOP聯盟(aopalliance.org)定義,它只是一個標記接口,用來強調概念,沒有定義任何功能(或者說沒有定義增強方式或增強內容)。
//org.aopalliance.aop; public interface Advice {}AspectJ Advice
?AspectJ Advice是使用AspectJ注解的Advice。對應的Advice接口都是標記接口。
AbstractAspectJAdvice
AspectJ Advice,調用Advice方法:invokeAdviceMethod。
protected Object invokeAdviceMethod(@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)throws Throwable {return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));}//最終調用。object參數為代理對象。 return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);Interceptor
?AOP聯盟在Advice的基礎上擴展定義了子接口——Interceptor(攔截器)。攔截器定義了通知的增強方式,也就是通過對Joinpoint(連接點)的攔截。
public interface Interceptor extends Advice {}Interceptor接口也在強調概念而非功能,也是一個標記接口。?由Interceptor擴展出的ConstructorInterceptor和MethodInterceptor兩個子接口,才具體定義了攔截方式。它們一個用于攔截構造方法,一個用于攔截普通方法。
public interface ConstructorInterceptor extends Interceptor {Object construct(ConstructorInvocation invocation) throws Throwable;}?
@FunctionalInterface public interface MethodInterceptor extends Interceptor {Object invoke(MethodInvocation invocation) throws Throwable;}spring框架并沒有支持AOP聯盟對構造方法的攔截,原因很簡單,spring框架本身,通過BeanPostProcessor的定義,對bean的生命周期擴展已經很充分了。
????? ? MethodInterceptor只定義了增強方式,我們可以通過實現此接口,自定義具體的增強內容。當然,spring框架也提供了3種預定義的增強內容——BeforeAdvice(前置通知)、AfterAdvice(后置通知)和DynamicIntroductionAdvice(動態引介通知)。BeforeAdvice和AfterAdvice更確切地說是定義了增強內容的執行時機(方法調用之前還是之后);而DynamicIntroductionAdvice比較特殊,它可以編輯目標類要實現的接口列表。最后,spring預定義的通知還是要通過對應的適配器,適配成MethodInterceptor接口類型的對象(如:MethodBeforeAdviceInterceptor負責適配MethodBeforeAdvice)。
?
重點介紹幾個常用攔截器:
? ? ? ? MethodBeforeAdvice(前置通知,其父接口是BeforeAdvice)接口的適配器,用于支持spring預定義的前置通知,在目標方法調用前調用MethodBeforeAdvice.before()。
? ? ? ? AspectJ框架的后置通知實現,在目標方法執行結束后,return之前,調用配置指定的方法(注意:此方法調用被寫在finally塊中,無論如何都會得到執行)。
? ? ? ? AfterReturningAdvice接口的適配器,用于支持spring預定義的后置通知,在目標方法執行結束后,return之前,調用AfterReturningAdvice.afterReturning()執行(注意:如果目標方法拋出異常,則不會執行這個方法)。
? ? ? ? AspectJ框架的異常通知,當目標方法執行時產生異常的時候,指定配置指定的方法。
? ? ? ? AspectJ框架的環繞通知,直接執行配置指定的方法。
? ? ? ? spring框架預定義的異常通知的適配器,此適配器接受任意類型的對象,但是要求對象所在類必須聲明public的名稱為afterThrowing,且參數個數為1個或4個,且最后一個參數為Throwable類型的方法。該適配器會保存該Throwable對象的實際類型到該方法之間的映射,當目標方法執行產生異常時,根據產生的異常類型找到對應的通知方法進行調用。
? ? ? ? 通過構造方法傳入指定的引介對象,每當調用的目標方法是引介接口定義的方法時,都會調用該引介對象的對應方法。
? ? ? ? 通過構造函數傳入指定的引介接口和接口對應的實現類,該攔截器會為每個目標對象創建新的引介對象(通過調用實現類的默認無參構造)。當調用的方法是引介接口定義的方法時,則調用該新建的引介對象對應的方法。
AdvisorAdapter
Advisor的適配器。默認有3個實現:AfterReturningAdviceAdapter,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter。
public interface AdvisorAdapter { //是否支持boolean supportsAdvice(Advice advice); //返回攔截器MethodInterceptor getInterceptor(Advisor advisor); }TargetSource
TargetSource(目標源)是被代理的target(目標對象)實例的來源。TargetSource被用于獲取當前MethodInvocation(方法調用)所需要的target(目標對象),這個target通過反射的方式被調用(如:method.invode(target,args))。換句話說,proxy(代理對象)代理的不是target,而是TargetSource,這點非常重要!!!
????? ? 那么問題來了:為什么SpringAOP代理不直接代理target,而需要通過代理TargetSource(target的來源,其內部持有target),間接代理target呢?
????????通常情況下,一個proxy(代理對象)只能代理一個target,每次方法調用的目標也是唯一固定的target。但是,如果讓proxy代理TargetSource,可以使得每次方法調用的target實例都不同(當然也可以相同,這取決于TargetSource實現)。這種機制使得方法調用變得靈活,可以擴展出很多高級功能,如:target pool(目標對象池)、hot swap(運行時目標對象熱替換),等等。
????? ? 接下來要說的另外一點,可能會顛覆你的既有認知:TargetSource組件本身與SpringIoC容器無關,換句話說,target的生命周期不一定是受spring容器管理的,我們以往的XML中的AOP配置,只是對受容器管理的bean而言的,我們當然可以手動創建一個target,同時使用Spring的AOP框架(而不使用IoC容器)。
public interface TargetClassAware {Class<?> getTargetClass();}public interface TargetSource extends TargetClassAware {/**返回targets類型。*/@Override@NullableClass<?> getTargetClass();/**target不變則返回true。getTarget返回同一個對象。*/boolean isStatic();/**Return a target instance*/@NullableObject getTarget() throws Exception;/**釋放target對象(getTarget獲取到的)*/void releaseTarget(Object target) throws Exception;}不同的TargetSource實現類主要解決的是每次調用getTarget怎么返回的對象實例。
TargetSource包含4個簡單實現和3大類實現。
四個簡單實現包括:
靜態目標源,當不存在target目標對象,或者甚至連targetClass目標類都不存在(或未知)時,使用此類實例。
動態目標源,支持熱替換的目標源,支持spring應用運行時替換目標對象。
spring對JNDI管理bean的支持,static屬性可配置。
靜態目標源,單例目標源。Spring的AOP框架默認為受IoC容器管理的bean創建此目標源。換句話說,SingletonTargetSource、proxy與目標bean三者的聲明周期均相同。如果bean被配置為prototype,則spring會在每次getBean時創建新的SingletonTargetSource實例。
??? ? 三大類實現包括:
此類目標源基于IoC容器實現,也就是說target目標對象可以通過beanName從容器中獲取。此類又擴展出:
(1)SimpleBeanTargetSource:簡單實現,直接調用getBean從容器獲取目標對象;
(2)LazyInitTargetSource:延遲初始化目標源,子類可重寫postProcessTargetObject方法后置處理目標對象;
(3)AbstractPrototypeBasedTargetSource:原型bean目標源,此抽象類可確保beanName對應的bean的scope屬性為prototype。其子類做了簡單原型、池化原型、線程隔離原型這3種實現。
可刷新的目標源。此類實現可根據配置的刷新延遲時間,在每次獲取目標對象時自動刷新目標對象。
此類實現在調用getTarget()獲取時才創建目標對象。
總結
以上是生活随笔為你收集整理的Spring AOP源码解析(一)——核心概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot Transact
- 下一篇: 高性能Mysql--Schema与数据类