橫切關注點:分佈於應用中多處的功能java
面向切面編程AOP:將橫切關注點與業務邏輯相分離正則表達式
在使用面向切面編程時,仍在一個地方定義通用功能,可是能夠經過聲明的方式定義這個功能以何種方式在何處應用,而無需修改受影響的類。spring
橫切關注點能夠被模塊化爲特殊的類,這些類被稱爲切面。
express
好處:編程
每一個關注點集中於一處,而不是分散到多處代碼中app
服務模塊更加簡潔,由於它們只包含主要關注點的代碼,次要關注點被轉移到切面中了框架
切面的工做被稱爲通知。
eclipse
通知定義了切面是什麼以及什麼時候使用。
模塊化
Spring切面能夠應用的5種類型的通知:this
Before——在方法被調用以前調用通知
After——在方法完成以後調用通知,不管方法執行是否成功。
After-returning——在方法成功執行以後調用通知。
After-throwing——在方法拋出異常後調用通知。
Around——通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲。
鏈接點是在應用執行過程當中可以插入切面的一個點,這個店能夠是調用方法時、拋出異常時、甚至修改一個字段時。
切點有助於縮小切面所通知鏈接點的範圍,切點的定義會匹配通知所要織入的一個或多個鏈接點。一般使用明確的類名和方法名來指定這些切點,或是利用正則表達式定義匹配的類和方法名來指定這些切點。
切面是通知和切點的結合,通知和切點共同定義了關於切面的所有內容——它是什麼、在什麼時候和何處完成其功能。
引入容許咱們向現有的類添加新方法或屬性。
織入是將切面應用到目標對象來建立新的代理對象的過程。
在目標對象的生命週期裏有多個點能夠進行織入:
編譯期——切面在目標類編譯時被織入。這種方式須要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
類加載期——切面在目標類加載到JVM時被織入。
運行期——切面在應用運行的某個時候被織入。通常狀況下,在織入切面時,AOP容器會爲目標對象動態地建立一個代理對象,Spring AOP就是以這種方式織入切面的。
並非全部AOP框架都是同樣的,有些容許對字段修飾符級別應用通知,而另外一些只支持與方法調用相關的鏈接點。它們在織入切面的方式和時機也有不一樣。但不管如何,建立切點來定義切面織入的鏈接點是AOP框架的基本功能。
AOP世界三足鼎立格局:
AspectJ(http://eclipse.org/aspectj);
JBoss AOP(http://www.jboss.org/jbossaop);
Spring AOP(http://www.springframework.org)
Spring提供了4種各具特點的AOP支持:
基於代理的經典AOP;
@AspectJ註解驅動的切面;
純POJO切面;
注入式AspectJ切面(適合Spring各版本)。
前3種都是Spring基於代理的AOP變體,所以,Spring對AOP的支持侷限於方法攔截。超過了簡單方法攔截的範疇,考慮在AspectJ裏面實現切面,利用Spring的DI把Spring Bean注入到AspectJ切面中。
Spring AOP框架的關鍵點:
Spring通知是Java類編寫的,定義通知所應用的切點一般在Spring配置文件裏採用XML來編寫的。
Spring在運行期通知對象,代理類封裝了目標類,並攔截被通知的方法的調用,再將調用轉發給真正的目標Bean。當攔截到方法調用時,在調用目標Bean方法以前,代理會執行切面邏輯。
Spring只支持方法鏈接點,由於Spring基於動態代理,因此Spring只支持方法鏈接點。這與其餘AOP框架是不一樣的,例如AspectJ和Jboss,除了方法切點,它們還提供了字段和構造器接入點。
切點用於準肯定位應該在什麼地方應用切面的通知,切點和通知是切面的最基本元素。
在Spring AOP中須要使用AspectJ的切點表達式語言來定義切點。關於Spring AOP的AspectJ切點,最重要的一點是Spring僅支持AspectJ切點指示器(pointcut designator)的一個子集。Spring是基於代理的,而某些切點表達式是與基於代理的AOP無關的。
Spring AOP 所支持的AspectJ切點指示器:
AspectJ指示器 | 描述 |
arg() |
限制鏈接點匹配參數爲指定類型的執行方法 |
@arg() | 限制鏈接點匹配參數由指定註解標註的執行方法 |
execution() | 用於匹配是鏈接點的執行方法 |
this() | 限制鏈接點匹配AOP代理的Bean引用爲指定類型的類 |
target() | 限制鏈接點匹配目標對象爲指定類型的類 |
@target() | 限制鏈接點匹配特定的執行對象,這些對象對應的類要具有指定類型的註解 |
within() | 限制鏈接點匹配指定的類型 |
@within() | 限制鏈接點匹配指定註解所標註的類型(當使用Spring AOP時,方法定義由指定的註解所標註的類裏) |
@annotation | 限制匹配帶有指定註解鏈接點 |
在Spring中嘗試使用AspectJ其餘指示器時,將會拋出IllegalArgumentException異常。
execution指示器是惟一的執行匹配,而其餘的指示器都用於限制匹配的,它是最主要使用的指示器。
使用AspectJ切點表達式來定位:
使用within()指示器來限制匹配:
由於在xml中&有特殊含義,因此在使用Spring的基於xml配置來描述切點時,可使用and代替&&,or代替||和not代替!。
該指示器容許咱們在切點表達式中使用Bean的ID來標識Bean。
如:
在執行Instrument的play()方法時應用通知,但限定Bean的ID爲eddie。
Spring的AOP配置元素:
AOP配置元素 | 描述 |
<aop:advisor> | 定義AOP通知器 |
<aop:after> | 定義AOP後置通知(無論被通知的方法是否執行成功) |
<aop:after-returning> | 定義AOP after-returning通知 |
<aop:after-throwing> | 定義AOP after-throwing 通知 |
<aop:around> | 定義AOP環繞通知 |
<aop:aspect> | 定義切面 |
<aop:aspectj-autoproxy> | 啓用@AspectJ註解驅動的切面 |
<aop:before> | 定義AOP前置通知 |
<aop:config> | 頂層的AOP配置元素,大多數<aop:*>元素必須包含在<aop:config>元素內 |
<aop:declare-parents> | 爲被通知的對象引入額外的接口,並透明地實現 |
<aop:pointcut> | 定義切點 |
<aop:config> <!--引用audience Bean--> <aop:aspect ref="audience"> <!--表演以前--> <aop:before pointcut="execution(* com.springinaction.springdol.Performer.perform(..))" method="takeSeats" /> <!--表演以後--> <aop:after-returning pointcut="execution(* com.springinaction.springdol.Performer.perform(..))" method="applaud" /> <!--表演失敗以後--> <aop:after-throwing pointcut="execution(* com.springinaction.springdol.Performer.perform(..))" method="demandRefund" /> </aop:config>
大多數的AOP配置元素必須在<aop:config>元素的上下文內使用,在<aop:config>元素內,能夠聲明一個或多個通知器、切面或者切點。
爲避免重複定義切點,可使用<aop:pointcut>元素定義一個命名切點:
<aop:config> <!--引用audience Bean--> <aop:aspect ref="audience"> <!--定義切點--> <aop:pointcut id="performance" expression="(* com.springinaction.springdol.Performer.perform(..))" /> <!--表演以前--> <aop:before pointcut-ref="performance" method="takeSeats" /> <!--表演以後--> <aop:after-returning pointcut-ref="performance" method="applaud" /> <!--表演失敗以後--> <aop:after-throwing pointcut-ref="performance" method="demandRefund" /> </aop:aspect> </aop:config>
利用<aop:around>來聲明環繞通知,能夠完成前置通知和後置通知所實現的相同功能,可是隻須要在一個方法中實現。
例如:
在切面audience中,watchPerformance()方法提供了AOP環繞通知,包含了以前前置和後置等通知方法的全部邏輯。
public void watchPerformance(ProceedingJoinPoint joinpoint) { try{ //表演以前 do something //... //執行被通知的方法 joinpoint.proceed(); //表演以後 do something //... }catch (Throwable t) { //表演失敗以後 do something //... } }
ProceedingJoinPoint做爲方法的入參,這個對象能讓咱們在通知裏調用被通知方法。必須調用proceed()方法,不然通知將會阻止被通知的方法。
聲明環繞通知:
<aop:config> <!--引用audience Bean--> <aop:aspect ref="audience"> <!--定義切點--> <aop:pointcut id="performance" expression="(* com.springinaction.springdol.Performer.perform(..))" /> <!--聲明環繞通知--> <aop:around pointcut-ref="performance" method="watchPerformance" /> </aop:aspect> </aop:config
利用<aop:declare-parents>
經過使用@AspectJ註解,使其不須要任何額外的類或Bean聲明就能將Audience類轉換爲一個切面。
@Pointcut註解用於定義一個能夠在@AspectJ切面內可重用的切點,其值是一個AspectJ切點表達式。切點的名稱來源於註解所應用的方法名稱,所以,該切點的名稱爲performance(),該方法的實際內容不重要,能夠爲空的,自己只是一個標識,供@Pointcut註解依附。
須要在Spring上下文中聲明一個自動代理Bean,該Bean知道如何把@AspectJ註解所標註的Bean轉變爲代理通知。爲此,Spring自帶了名爲AnnotationAwareAspectJAutoProxyCreator的自動代理建立類,咱們能夠在Spring上下文中把AnnotationAwareAspectJAutoProxyCreator註冊爲一個Bean,爲了簡化,Spring在aop命名空間中提供了一個自定義的配置元素:<aop:aspectj-autoproxy />。
<aop:aspectj-autoproxy />將在Spring上下文中建立一個AnnotationAwareAspectJAutoProxyCreator類,它會自動代理一些Bean,這些Bean的方法須要與使用@AspectJ註解的Bean中所定義的切點相匹配,而這些切點又是@Pointcut註解定義出來的。
記住,<aop:aspectj-autoproxy>僅僅使用@AspectJ註解做爲指引來建立基於代理的切面,但本質上還是一個Spring風格的切面。
在這裏,@Around註解標識了watchPerformance()方法將被做爲環繞通知應用於performance()切點。
<aop:pointcut>元素變爲@Pointcut註解,而<aop:before>元素變爲@Before註解。在這裏,惟一發生顯著變化的是@AspectJ可以依靠Java語法來判斷爲通知所傳遞參數的細節,所以這裏並不須要與<aop:before>元素的arg-names屬性所對應的註解。
等價於<aop:declare-parents>的註解是@AspectJ的@DeclareParents,在基於@AspectJ註解所標註的類內使用時,@DeclareParents工做方式幾乎等同於<aop:declare-parents>。
@DeclareParents註解由3部分組成:
value屬性等同於<aop:declare-parents>的type-matching屬性,標識應該被注入指定接口的Bean的類型;
defaultImpl屬性等同於<aop:declare-parents>的default-impl屬性,標識該類提供所引入接口的實現;
由@DeclareParents註解所標註的static屬性指定了將被引入的接口。
雖然Spring AOP可以知足許多切面需求,但與AspectJ相比,Spring AOP是一個功能比較弱的AOP解決方案,AspectJ提供了Spring AOP所不能支持的許多類型的切點。