一、增強(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)的某些具體方法的能力。

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):

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)。

- 如果在類(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é)

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)中,工作原理如圖所示:

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>