學習AOP有段時間了,一直沒空總結一下,致使有些知識點都遺忘了,以後會把之前學過的Spring核心相關的知識點總結一輪...java
先大致介紹下Spring AOP的特色(均摘自"Spring in action第四版"):正則表達式
Spring支持了AOP,另外還有不少實現了AOP的技術,例如AspectJ,它補充了Spring AOP框架的功能,他們之間有着大量的協做,並且Spring AOP中大量借鑑了AspectJ項目,Spring AOP相對粗粒度,而AspectJ提供更強大更細粒度的控制,以及更豐富的AOP工具集,但須要額外的語法學習;spring
Spring借鑑了AspectJ的切面,以提供註解驅動的AOP,編程模型幾乎與編寫成熟的AspectJ註解切面徹底一致。這種AOP風格的好處在於可以不使用XML來完成功能。Spring AOP構建在動態代理之上,所以,Spring對AOP的支持侷限於方法攔截;若是你對AOP的需求超過了建黨方法調用(如構造器或屬性攔截),那麼你須要AspectJ來實現切面;編程
Spring提供了4種類型的AOP支持:app
基於代理的經典Spring AOP;框架
純POJO切面;工具
@AspectJ註解驅動的切面;學習
注入式AspectJ切面(適用於Spring各版本);spa
AOP相關的術語(概念):3d
1.通知(Advice):
切面的工做被稱爲通知,通知定義了切面是什麼以及什麼時候使用;
分5類:
前置通知(Before) | 在目標方法被調用以前調用通知功能; |
後置通知(After) | 在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼; |
返回通知(After-returning) | 在目標方法成功執行以後調用通知; |
異常通知(After-throwing) | 在目標方法拋出異常後調用通知; |
環繞通知(Around) | 通知包裹了被通知的方法,在被通知的方法調用以前和調用以後 |
2.鏈接點(Join point)
應用通知的時機被稱爲鏈接點;鏈接點是在應用執行過程當中可以插入切面的一個點。這個點能夠是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼能夠利用這些點插入到應用的正常流程之中,並添加新的行爲;
3.切點(Poincut)
切點定義了切面的在何處實施;一般使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名來指定;
4.切面Aspect
是通知(什麼時候,作什麼)和切點(何處)的結合;
5.引入(Introduction)
容許咱們向現有的類添加新方法或新屬性;
6.織入(Weaving)
把切面應用到目標對象並建立新的代理對象的過程
3種織入時機:
編譯期 | 在目標類編譯時被織入,AspectJ的織入編譯器就是以這種方式織入的 |
類加載期 | 在目標類加載到JVM中時被織入,AspectJ5的加載時織入(load-time weaving,LTW)支持 |
運行期 | 在運行的某個時刻被織入,通常狀況下AOP容器會爲目標對象動態建立一個代理對象,Spring AOP就支持這種方式 |
Spring AOP是基於動態代理的:
經過在代理類中包裹切面,Spring在運行期把切面織入到Spring管理的bean中。以下圖所
示,代理類封裝了目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。當
代理攔截到方法調用時,在調用目標bean方法以前,會執行切面邏輯。
編寫切點(若干圖例瞭解切點表達式):
咱們使用execution()指示器選擇Performance的perform()方法。方法表達式以「*」號開始,代表了咱們不關心方法返回值的類型。而後,咱們指定了全限定類名和方法名。對於方法參數列表,咱們使用兩個點號(..)代表切點要選擇任意的perform()方法,不管該方法的入參是什麼。
咱們使用了「&&」操做符把execution()和within()指示器鏈接在一塊兒造成與(and)關係(切點必須匹配全部的指示器)。相似地,咱們可使用「||」操做符來標識或(or)關係,而使用「!」操做符來標識非(not)操做。由於「&」在XML中有特殊含義,因此在Spring的XML配置裏面描述切點時,咱們可使用and來代替「&&」。一樣,or和not能夠分別用來代替「||」和「!」。
在這裏,咱們但願在執行Performance的perform()方法時應用通知,但限定bean的ID爲woodstock。
下面正式開始建立切面:
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class Audience { @Before("execution(** concert.Performance.perform(..))") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("execution(** concert.Performance.perform(..))") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("execution(** concert.Performance.perform(..))") public void applause() { System.out.println("CLAP CLAP CLAP..."); } @AfterThrowing("execution(** concert.Performance.perform(..))") public void demandRefund() { System.out.println("Demanding a refund"); } }
Audience類使用@AspectJ註解進行了標註。該註解代表Audience不只僅是一個POJO,仍是一個切面。Audience類中的方法都使用註解來定義切面的具體行爲。Audience有四個方法,定義了一個觀衆在觀看演出時可能會作的事情。在演出以前,觀衆要就坐(takeSeats())並將手機調至靜音狀態(silenceCellPhones();
AspectJ提供了五個註解來定義通知
這樣,一個切面就定義好了,但咱們四個註解使用的表達式都同樣,能夠用@Pointcut註解提供表達式的引用:
1.提供一個空方法,在上面增長註解:@Pointcut("execution(** concert.Performance.perform(..))");
2.其它註解引用,例如: @Before("上面定義的空方法名()");
光這樣還不夠,下面要裝配Audience類成爲一個bean:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy //氣筒AspectJ自動代理 @ComponentScan public class ConcertConfig { //bean的配置類 @Bean public Audience audience() { //配置Audience類成爲一個Bean return new Audience(); } }
以上就實現了前置後置分離通知的切面;
接下在介紹另外一種:環繞通知的寫法(邏輯不復雜的時候建議用這種方式,更加直觀,一旦邏輯複雜,可讀性會不好):
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Around; import org.aspectj.lang.Aspect; import org.aspectj.lang.Pointcut; @Aspect public void Audience { @Pointcut("execution(** concert.Performance.perform(..))") public void performance(){} @Around("performance()") public void watchPerformance(PerceedingJoinPoint jp){ try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); jp.proceed(); //調用被通知的方法(能夠屢次調用) System.out.println("CLAP CLAP CLAP..."); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
以上總結的是不帶參數構造切面的狀況
接下來介紹若是在通知上符加參數的狀況:
import java.util.HashMap; import java.util.Map; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class TrackCounter { private Map<Integer, Interger> trackCounts = new HashMap<Integer, Integer>(); @Pointcut( "execution(* soundsystem.CompactDisc.playTrack(int))" + "&& args(trackNumber)") public void trackPlayed(int trackNumber) {} @Before("trackPlayed(trackNumber)") public void countTrack(int trackNumber){ int currentCount = getPlayCOunt(trackNumber); trackCounts.put(trackNumber, currentCount + 1); } public int getPlayCount(int trackNumber) { return trackCOunts.containKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }
圖例來解釋表達式: