Spring AOP初步總結(一)
學習AOP有段時間了,一直沒空總結一下,導致有些知識點都遺忘了,之後會把以前學過的Spring核心相關的知識點總結一輪...
先大體介紹下Spring AOP的特點(均摘自"Spring in action第四版"):
Spring支持了AOP,另外還有很多實現了AOP的技術,例如AspectJ,它補充了Spring AOP框架的功能,他們之間有著大量的協作,而且Spring AOP中大量借鑒了AspectJ項目,Spring AOP相對粗粒度,而AspectJ提供更強大更細粒度的控制,以及更豐富的AOP工具集,但需要額外的語法學習;
Spring借鑒了AspectJ的切面,以提供註解驅動的AOP,編程模型幾乎與編寫成熟的AspectJ註解切面完全一致。這種AOP風格的好處在於能夠不使用XML來完成功能。Spring AOP構建在動態代理之上,因此,Spring對AOP的支持局限於方法攔截;如果你對AOP的需求超過了建黨方法調用(如構造器或屬性攔截),那麽你需要AspectJ來實現切面;
Spring提供了4種類型的AOP支持:
基於代理的經典Spring AOP;
純POJO切面;
@AspectJ註解驅動的切面;
註入式AspectJ切面(適用於Spring各版本);
復習下AOP相關的術語(概念):
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; } }
圖例來解釋表達式:
Spring AOP初步總結(一)