Spring AOP

一、增強(qiáng)類(lèi)

1 前置增強(qiáng)(MethodBeforeAdvice)
重寫(xiě)before(Method method,Object[] args,Object obj)方法,method為目標(biāo)類(lèi)的方法;args為目標(biāo)類(lèi)方法參數(shù);obj為目標(biāo)類(lèi)實(shí)例。

2 后置增強(qiáng)(AfterReturningAdvice)
重寫(xiě)afterReturning(Object returnObj,Method method,Object[] args,Object obj)方法,returnObj為目標(biāo)方法返回的結(jié)果,其余參數(shù)合和befor方法一致。

3 環(huán)繞增強(qiáng)(MethodInterceptor)
重寫(xiě)invoke(MethodInvocation)方法,MethodInvocation不僅封裝了目標(biāo)方法及其入?yún)?shù)組,還封裝了目標(biāo)方法所在的實(shí)力對(duì)象,通過(guò)MethodInvocation的getArguments方法可以獲取目標(biāo)方法的入?yún)?shù),通過(guò)proceed方法反射調(diào)用目標(biāo)實(shí)例相應(yīng)的方法。

4 異常拋出增強(qiáng)(ThrowsAdvice)
重寫(xiě)afterThrowing(Method method,Object[] args,Object target,Exception ex)方法,方法參數(shù)規(guī)定如下:前三個(gè)參數(shù)(method,args,target)是可選的(要么三個(gè)全部提供,要么都不不提供),最后一個(gè)參數(shù)是Throwable或其子類(lèi)。

5 引介增強(qiáng)(IntroductionInterceptor)
該接口沒(méi)有定義任何方法,Spring為該接口提供了DelegatingIntroductionInterceptor實(shí)現(xiàn)類(lèi),一般情況下通過(guò)繼承該類(lèi),覆蓋invoke(MethodInvocation mi)方法來(lái)定義自己的引介增強(qiáng)類(lèi)。

二、切面

介紹增強(qiáng)時(shí),我們注意到增強(qiáng)被織入到目標(biāo)類(lèi)的所有方法中,如果我們要有選擇地進(jìn)行織入,就需要使用切點(diǎn)進(jìn)行目標(biāo)連接點(diǎn)的定位了。

增強(qiáng)提供了連接點(diǎn)的方位信息:如織入到方法前、方法后,而切點(diǎn)進(jìn)一步描述織入到哪些類(lèi)的哪些方法上。

Spring通過(guò)PointCut接口描述切點(diǎn),PointCut由ClassFilter和MethodMatcher構(gòu)成,它通過(guò)ClassFilter定位到類(lèi),通過(guò)MethodMatcher定位到方法,這樣PointCut就擁有了描述某些類(lèi)的某些具體方法的能力。

PiontCut類(lèi)關(guān)系圖

ClassFilter只定義了matches(Class clazz),其參數(shù)代表一個(gè)被檢測(cè)的類(lèi),該方法判別被檢測(cè)的類(lèi)時(shí)候匹配過(guò)濾條件。

MethodMatcher支持靜態(tài)方法匹配以及動(dòng)態(tài)方法匹配。靜態(tài)方法匹配僅對(duì)方法名進(jìn)行匹配,且只會(huì)判別一次;動(dòng)態(tài)方法匹配會(huì)在運(yùn)行時(shí)檢查方法入?yún)⒌闹?,所以每次調(diào)用方法都會(huì)進(jìn)行判斷。

切點(diǎn)類(lèi)型

Spring提供了6中切點(diǎn):

  • 靜態(tài)方法切點(diǎn)
    StaticMethodMatcherPointCut,默認(rèn)匹配所有類(lèi)。兩個(gè)主要的子類(lèi)是NamedMatchMethodPointCut以及AbstractRegexpMatchMethodPointCut
  • 動(dòng)態(tài)方法切點(diǎn)
    DynamicMethodMatcherPointCut
  • 注解切點(diǎn)
    AnnotationMatchingPointCut,支持在Bean中直接通過(guò)注解標(biāo)簽定義切點(diǎn)
  • 表達(dá)式切點(diǎn)
    ExpressionPointCut,為了支持AspectJ切點(diǎn)表達(dá)式語(yǔ)法定義的接口
  • 流程切點(diǎn)
    ControlFlowPointCut,根據(jù)程序執(zhí)行堆棧信息查看目標(biāo)方法是否由某一個(gè)方法直接或者間接調(diào)用,以此判斷是否為匹配的連接點(diǎn)
  • 復(fù)合切點(diǎn)
    ComposablePointCut,為創(chuàng)建多個(gè)切點(diǎn)而提供的方便操作類(lèi)

切面類(lèi)型

增強(qiáng)包含橫切代碼,又包含部分連接點(diǎn)信息,所以我們可以通過(guò)增強(qiáng)類(lèi)生成一個(gè)切面。切點(diǎn)只包含類(lèi)和方法信息,所以要結(jié)合增強(qiáng)才能制作出切面。

  • 一般切面
    Advisor,僅包含一個(gè)Advice。即切面僅包含一個(gè)增強(qiáng),由于它代表的連接點(diǎn)是目標(biāo)類(lèi)的所有方法,所以一般不直接使用。
  • 切點(diǎn)切面
    PointAdvisor,具有切點(diǎn)的切面,包含Advice和PointCut兩個(gè)類(lèi)。
  • 引介切面
    IntroductionAdvisor

三、織入切面到目標(biāo)類(lèi)的方法

1 通過(guò)ProxyFactory代理工廠

Spring中通過(guò)使用ProxyFactory將增強(qiáng)織入到目標(biāo)類(lèi)中,ProxyFactory內(nèi)部就是使用JDK代理或CGLib代理技術(shù),將增強(qiáng)織入到目標(biāo)類(lèi)中。

Spring定義了AopProxy接口,并提供了兩個(gè)final類(lèi)型的實(shí)現(xiàn)類(lèi):

AopProxy類(lèi)結(jié)構(gòu)

2 通過(guò)Spring配置
在配置文件中定義如下Bean:

<bean id="objName" class="org.springframework.aop.framework.ProxyFactoryBean" 
    p:proxyInterfaces="代理接口,如果是多個(gè)接口,使用list元素"
    p:interceptorNames="指定使用的增強(qiáng)"
    p:target-ref="指定對(duì)那個(gè)Bean進(jìn)行代理"
    p:proxyTargetClass="是否對(duì)類(lèi)進(jìn)行代理,設(shè)置為true,使用CGLib代理" />

3 自動(dòng)創(chuàng)建代理

Spring提供了自動(dòng)創(chuàng)建代理機(jī)制,讓容器為我們自動(dòng)生成代理。在內(nèi)部,Spring使用BeanPostProcessor自動(dòng)完成這項(xiàng)工作。

基于BeanPostProcessor的自動(dòng)代理創(chuàng)建器的實(shí)現(xiàn)類(lèi),將根據(jù)一些規(guī)則在容器實(shí)例化Bean時(shí)為匹配的Bean生成代理實(shí)例。代理創(chuàng)建器可以分為以下三類(lèi):

  • 基于Bean配置名規(guī)則的自動(dòng)代理器
    允許為一組特定配置名的Bean自動(dòng)創(chuàng)建代理實(shí)例,實(shí)現(xiàn)類(lèi)為BeanNameAutoProxyCreator
  • 基于Advisor匹配機(jī)制的自動(dòng)代理創(chuàng)建器
    對(duì)容器中所有的Advisor進(jìn)行掃描,自動(dòng)將這些切面應(yīng)用到匹配的Bean中,實(shí)現(xiàn)類(lèi)為DefaultAdvisorAutoProxyCreator
  • 基于注解的自動(dòng)代理創(chuàng)建器
    為包含注解的Bean自動(dòng)創(chuàng)建代理實(shí)例,實(shí)現(xiàn)類(lèi)為AnnotationAwareAspectJAutoProxyCreator

四、基于注解的AOP

在基于注解的方式中,我們利用@AspectJ來(lái)描述切點(diǎn)、增強(qiáng),和之前的PointCut和Advice相比,兩者只是表述方式不同。
下面是一個(gè)例子:

我們驚奇的發(fā)現(xiàn),這個(gè)切面只是一個(gè)普通的POJO,特殊的地方在于標(biāo)注了@AspectJ注解。

如何通過(guò)配置使用@AspectJ切面

在上一節(jié)中我們介紹的自動(dòng)代理創(chuàng)建器,其中AnnotationAwareAspectJAutoProxyCreator可以將@AspectJ注解的切面織入到目標(biāo)Bean中。

如果使用基于Schema的aop命名空間進(jìn)行配置,那就更加簡(jiǎn)單了:

首先在配置文件中引入aop的命名空間,如①、②出所示,然后通過(guò)aop命名空間的

<aop:aspectj-autoproxy>

自動(dòng)為Spring容器中那些匹配@AspectJ切面的Bean創(chuàng)建代理,完成切面織入(Spring內(nèi)部依舊采用AnnotationAwareAspectJAutoProxyCreator進(jìn)行代理的創(chuàng)建工作)。

@AspectJ語(yǔ)法基礎(chǔ)

1 增強(qiáng)類(lèi)型

@Before
前置增強(qiáng),相當(dāng)于BeforeAdvice,有兩個(gè)成員:

  • value:定義切點(diǎn)
  • argNames:指定注解所標(biāo)注增強(qiáng)方法的參數(shù)名(兩者名字必須完全相同),多個(gè)參數(shù)用逗號(hào)分離

@AfterReturning
后置增強(qiáng),相當(dāng)于AfterReturningAdvice,有四個(gè)成員:

  • value:定義切點(diǎn)
  • pointcut:表示切點(diǎn)信息,如果顯示定義將覆蓋value的值
  • returning:將目標(biāo)對(duì)象方法的返回值綁定給增強(qiáng)方法
  • argNames:如前所述

@Around
環(huán)繞增強(qiáng),相當(dāng)于MethodInterceptor,有兩個(gè)成員:

  • value:定義切點(diǎn)
  • argNames:如前所述

@AfterThrowing
環(huán)繞增強(qiáng),相當(dāng)于ThrowsAdvice,有四個(gè)成員:

  • value:定義切點(diǎn)
  • pointcut:表示切點(diǎn)信息,如果顯示定義將覆蓋value的值
  • throwing:將目標(biāo)對(duì)象方法的返回值綁定給增強(qiáng)方法
  • argNames:如前所述

@After
final增強(qiáng),不管是異常拋出還是正常退出,該增強(qiáng)都會(huì)執(zhí)行,有兩個(gè)成員:

  • value:定義切點(diǎn)
  • argNames:如前所述

@DeclareParents
引介增強(qiáng),相當(dāng)于IntroductionInterceptor,有兩個(gè)成員:

  • value:定義切點(diǎn),表示在哪個(gè)目標(biāo)類(lèi)上添加引介增強(qiáng)
  • defaultImpl:默認(rèn)的接口實(shí)現(xiàn)類(lèi)

2 切點(diǎn)函數(shù)表達(dá)式
Spring支持9個(gè)@AspectJ切點(diǎn)表達(dá)式函數(shù),大致分為四種類(lèi)型:

  • 方法切點(diǎn)函數(shù):通過(guò)描述目標(biāo)類(lèi)方法信息定義連接點(diǎn)
  • 方法入?yún)⑶悬c(diǎn)函數(shù):通過(guò)描述目標(biāo)類(lèi)方法入?yún)⑿畔⒍x連接點(diǎn)
  • 目標(biāo)類(lèi)切點(diǎn)函數(shù):通過(guò)描述目標(biāo)類(lèi)類(lèi)型信息定義連接點(diǎn)
  • 代理類(lèi)切點(diǎn)函數(shù):通過(guò)描述目標(biāo)類(lèi)的代理類(lèi)信息定義連接點(diǎn)

四種類(lèi)型的切點(diǎn)函數(shù)如下進(jìn)行說(shuō)明:

@annotation()

@annotation表示標(biāo)注了某個(gè)注解的所有方法。例如:

假設(shè)類(lèi)NaughtyWaiter#greetTo()方法標(biāo)注了@NeedTest注解,那么該方法將會(huì)被織入增強(qiáng)。

execution()

是最常用的切點(diǎn)函數(shù),語(yǔ)法如下:

execution(<修飾符模式>?<返回類(lèi)型模式><方法模式>(<參數(shù)模式>)<異常模式>?)

修飾符模式和異常模式是可選的。

  • execution(public * *(..)):匹配目標(biāo)類(lèi)所有public方法
  • execution(* *To(..)):匹配目標(biāo)類(lèi)所有以To為后綴的方法
  • execution(* Waiter.*(..)):匹配Waiter接口的所有方法
  • execution(* Waiter+.*(..)):匹配Waiter接口和其實(shí)現(xiàn)類(lèi)的所有方法
  • execution(* com.package.*(..)):匹配package包下所有類(lèi)的方法
  • execution(* com.package..*(..)):匹配package包及其子包下所有類(lèi)的方法
  • execution(* joke(String,int)):匹配joke(String,int)方法和其入?yún)?。如果方法中入?yún)㈩?lèi)型是java.lang包下的,可以直接使用類(lèi)名,否則使用全限定類(lèi)名

args()和@args()

args()接受一個(gè)類(lèi)名,表示目標(biāo)類(lèi)方法入?yún)?duì)象是指定類(lèi)時(shí)(包含子類(lèi)),切點(diǎn)匹配。例如:

args(com.lzn.Waiter),等價(jià)于execution(* *(com.lzn.Waiter+))

表示運(yùn)行時(shí)入?yún)⑹荳aiter類(lèi)型的方法,則匹配。

@args()接受一個(gè)注解類(lèi)的類(lèi)名,當(dāng)方法的運(yùn)行時(shí)入?yún)?duì)象標(biāo)注了指定的注解時(shí),方法匹配切點(diǎn)。

@args(M)匹配示意圖
  • 如果在類(lèi)繼承樹(shù)中注解點(diǎn)②高于入?yún)㈩?lèi)型點(diǎn)①,不會(huì)匹配
  • 如果在類(lèi)繼承樹(shù)中注解點(diǎn)②低于入?yún)㈩?lèi)型點(diǎn)①,匹配所在類(lèi)和其子類(lèi)

within()

within()定義的連接點(diǎn)是針對(duì)目標(biāo)類(lèi)而言的。語(yǔ)法如下所示:

within(<類(lèi)匹配模式>)

各種切面類(lèi)型總結(jié)

切點(diǎn)不同定義方式總結(jié)

LTW(Load Time Weaving)

AOP切面織入除了通過(guò)JDK動(dòng)態(tài)代理以及CGLib代理的方式實(shí)現(xiàn)之外,還可以通過(guò)在類(lèi)加載期通過(guò)字節(jié)碼編輯技術(shù)將切面植入到目標(biāo)類(lèi)中,這種方式叫做LTW。

Spring的LTW僅支持@AspectJ定義的切面,它利用類(lèi)路徑下的META_INF/aop.xml配置文件找到切面定義以及切面所要實(shí)施的候選目標(biāo)類(lèi)的信息,通過(guò)LoadTimeWeaver在ClassLoader加載類(lèi)文件時(shí),將切面織入到目標(biāo)類(lèi)中,工作原理如圖所示:

LTW工作原理

Spring利用特定web容器的ClassLoader,通過(guò)LoadTimeWeaver將Spring提供的ClassFileTransformer注冊(cè)到ClassLoader中。在類(lèi)加載時(shí)期,注冊(cè)的ClassFileTransformer讀取META_INF/aop.xml配置文件,獲取切面,對(duì)加載到VM中的Bean類(lèi)進(jìn)行字節(jié)碼轉(zhuǎn)換,織入切面。Spring容器初始化Bean實(shí)例時(shí),采用的Bean類(lèi)就是已經(jīng)織入切面的類(lèi)。

Spring的LoadTimeWeaver

大多數(shù)web應(yīng)用服務(wù)器(Tomcat除外)的ClassLoader都支持直接訪問(wèn)Instrument,無(wú)需通過(guò)javaagent參數(shù)指定代理,擁有這種能力的ClassLoader稱(chēng)為“組件使能”。通過(guò)“組件使能”功能,可以非常方便的訪問(wèn)到ClassLoader的Instrument。Spring利用了web應(yīng)用服務(wù)器類(lèi)加載的這個(gè)特性,為他們提供了專(zhuān)門(mén)的LoadTimeWeaver。以便向特定的ClassLoader注冊(cè)ClassFileTransformer,對(duì)類(lèi)進(jìn)行字節(jié)碼轉(zhuǎn)換,實(shí)現(xiàn)切面織入。

LoadTimeWeaver接口有三個(gè)方法:

  • void addTransformer(ClassFileTransformer transformer)
    添加一個(gè)ClassFileTransformer 到加載期織入器中
  • ClassLoader getInstrumentableClassLoader()
    我們知道JVM擁有Instrument組件,但這是JVM級(jí)別的。Spring對(duì)ClassLoader 進(jìn)行擴(kuò)展,讓他具有Instrument組件,以便只對(duì)ClassLoader 中的類(lèi)應(yīng)用ClassFileTransformer
  • getThrowawayClassLoader()
    返回一個(gè)丟棄的ClassLoader ,目的是使Instrument的作用范圍僅局限在本ClassLoader 中,而不影響父類(lèi)的ClassLoader

Spring只需在配置文件中加入一行配置就能啟用LoadTimeWeaver

<context:load-time-weaver>

使用LTW織入切面實(shí)例

第一步:定義切面

第二步:創(chuàng)建可被增強(qiáng)類(lèi)

第三步:Spring配置文件

<context:load-time-weaver>
<bean class="com.baobaotao.ltw.Waiter">

第四步:在src下創(chuàng)建META-INF目錄,并在該目錄下新增AspectJ的配置文件aop.xml:

<aspectj>
    <aspects>
        // 切面
        <aspect name="com.baobaotao.ltw.PreGreetingAspect" />
    </aspects>
    <weaver options="-showWeaveInfo 
        -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMeaasgeHandler">
        // 要織入增強(qiáng)的目標(biāo)類(lèi)
        <include within="com.baobaotao.ltw.*" />
    </aspect>
</aspectj>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • **** AOP 面向切面編程 底層原理 代理?。?! 今天AOP課程1、 Spring 傳統(tǒng) AOP2、 Spri...
    luweicheng24閱讀 1,500評(píng)論 0 1
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,533評(píng)論 19 139
  • 什么是AOP Aspect Oriented Programming:面向切面編程 什么時(shí)候會(huì)出現(xiàn)面向切面編程的需...
    守望者00閱讀 2,219評(píng)論 0 1
  • 2017/6/4 今天妹妹給我說(shuō)我爸媽好像吵架了,問(wèn)我怎么辦,我才發(fā)現(xiàn)我已經(jīng)好久都沒(méi)有回家了,上次是端午放假三天,...
    我的處女座閱讀 181評(píng)論 0 0
  • 酒店里,一個(gè)人。不能讓自己太無(wú)聊,找出一個(gè)指甲鉗,給自己修一下手指和腳趾。想起如若能在足浴城,有修腳師給自己修修腳...
    不再虛榮的胡晶閱讀 533評(píng)論 3 0

友情鏈接更多精彩內(nèi)容