標籤:支援 parent 空間 傳回值 declare code 載器 tor 列表
一.面向切面編程
??Spring的基礎是IOC和AOP,前面兩節對IOC和DI做了簡單總結,這裡再對AOP進行一個學習總結,Spring基礎就算有一個初步瞭解了。
??在軟體開發中,我們可能需要一些跟業務無關但是又必須做的東西,比如日誌,事務等,這些分佈於應用中多處的功能被稱為橫切關注點,通常橫切關注點從概念上是與應用的商務邏輯相分離的。如何將這些橫切關注點與商務邏輯在代碼層面進行分離,是面向切面編程(AOP)所要解決的。
? 橫切關注點可以被描述為影響應用多處的功能,切面能夠協助我們模組化橫切關注點。直觀呈現了橫切關注點的概念:
途中CourseService,StudentService,MiscService都需要類似安全、事務這樣的協助工具功能,這些協助工具功能就被稱為橫切關注點。
??繼承和委託是最常見的實現重用通用功能的物件導向技術。但是如果在整個程式中使用相同的基類繼承往往會導致一個脆弱的對象體系;而使用委託可能需要對委派物件進行複雜的調用。
? 切面提供了取代繼承和委託的另一種選擇,而且更加清晰簡潔。在面向切面編程時,我們任然在一個地方定義通用功能,但是我們可以通過聲明的方式定義這個功能以何種方式在何處應用,而無需修改受影響的類,受影響類完全感受不到切面的存在。
二.AOP常用術語
??下面是AOP中常用的名詞。
1. 通知(Advice)
??通知定義了切面是什麼以及何時使用。出了描述切面要完成的工作,通知還解決了何時執行這個工作的問題。Sping切面可以應用以下5種類型的通知。
- Before 在方法被調用之前調用通知
- After 在方法完成之後調用通知,無論方法執行是否成功
- After-returning 在方法成功執行後調用通知
- After-throwing 在方法拋出異常後調用通知
- Around 通知包裹了被通知的方法,在被通知的方法調用前和調用後執行
2.連接點(Joinpoint)
??應用可能有很多個時機應用通知,這些時機被稱為連接點。連接點是應用在執行過程中能夠插入切面的一個點,這個點可以是調用方法時、拋出異常時、甚至是修改欄位時。切面代碼可以利用這些切入到應用的正常流程中,並添加新的行為。
3.切點(Pointcut)
??切點定義了通知所要織入的一個或多個連接點。如果說通知定義了切面的“什麼”和“何時”,那麼切點就定義了“何處”。通常使用明確的類和方法名稱來指定切點,或者利用Regex定義匹配的類和方法來指定這些切點。有些AOP架構允許我們建立動態切點,可以更具運行時的策略來決定是否應用通知。
4.切面(Aspect)
??切面是通知和切點的結合。通知和切點定義了關於切面的全部內容,是什麼,在何時、何處完成其功能。
5.引入
??引入允許我們想現有的類添加新方法或屬性。即在無需修改現有類的情況下讓它們具有新的行為和狀態。
6.織入
??織入是將切面應用到目標對象來建立新的代理對象的過程。切面在指定的連接點被織入到目標對象中,在目標對象的生命週期裡有多個點可以進行織入。
- 編譯期:切面在目標類編譯時間被織入。這種方式需要特殊的編譯期,比如AspectJ的織入編譯期
- 類載入期:切面在目標類載入到JVM時被織入。這種方式需要特殊的載入器,它可以在目標類被引入應用之前增強該目標類的位元組碼,例如AspectJ5的LTW(load-time weaving)
- 運行期:切面在應用啟動並執行某個時刻被織入。一般情況下AOP容器會為目標對象動態建立一個代理對象
三.Spring AOP
??Spring在運行期通知對象,通過在代理類中包裹切面,Spring在運行期將切面織入到Spring管理的Bean中。代理類封裝了目標類,並攔截被通知的方法的調用,再將調用轉寄給真正的目標Bean。由於Spring是基於動態代理,所有Spring只支援方法連接點,如果需要方法攔截之外的連接點攔截,我們可以利用Aspect來協助SpringAOP。
??Spring在運行期通知對象,通過在代理類中包裹切面,Spring在運行期將切面織入到Spring管理的Bean中。代理類封裝了目標類,並攔截被通知的方法的調用,再將調用轉寄給真正的目標Bean。由於Spring是基於動態代理,所有Spring只支援方法連接點,如果需要方法攔截之外的連接點攔截,我們可以利用Aspect來協助SpringAOP。
1、定義切點
??在SpringAOP中,需要使用AspectJ的切點運算式語言來定義切點。Spring只支援AspectJ的部分切點指標,如下表所示:
| AspectJ指標 |
描述 |
| arg() |
限制連接點匹配參數為指定類型的執行方法 |
| @args() |
限制連接點匹配參數由指定註解標註的執行方法 |
| execution() |
用於匹配是連接點的執行方法 |
| this() |
限制連接點匹配AOP代理的Bean引用為指導類型的類 |
| target() |
限制連接點匹配目標對象為指定類型的類 |
| @target() |
限制連接點匹配特定的執行對象,這些對象對應的類要具備指定類型的註解 |
| within() |
限制連接點匹配指定的類型 |
| @within() |
限制連接點匹配指定註解所標註的類型(當使用SpringAOP時,方法定義在由指定的註解所標註的類裡) |
| @annotation |
限制匹配帶有指定註解連接點 |
| bean() |
使用Bean ID或Bean名稱作為參數來限制切點只匹配特定的Bean |
?其中只有execution指標是唯一的執行匹配,其他都是限制匹配。因此execution指標是
其中只有execution指標是唯一的執行匹配,其他都是限制匹配。因此execution指標是我們在編寫切點定義時最主要使用的指標。
2、編寫切點
??假設我們要使用execution()指標選擇Hello類的sayHello()方法,運算式如下:
execution(* com.test.Hello.sayHello(..))
方法運算式以*** 號開始,說明不管方法傳回值的類型。然後指定全限定類名和方法名。對於方法參數列表,我們使用()標識切點選擇任意的sayHello()方法,無論方法入參是什麼。
??同時我們可以使用&&(and),||(or),!(not)來串連指標,如下所示:
execution(* com.test.Hello.sayHello(..)) and !bean(xiaobu)
3、申明切面
??在經典Spring AOP中使用ProxyFactoryBean非常複雜,因此提供了申明式切面的選擇,在Spring的AOP配置命名空間中有如下配置元素:
| AOP配置元素 |
描述 |
| <aop:advisor > |
定義AOP通知器 |
| <aop:after > |
定義AOP後置通知(無論被通知方法是否執行成功) |
| <aop:after-returning > |
定義AOP after-returning通知 |
| <aop:after-throwing > |
定義after-throwing |
| <aop:around > |
定義AOP環繞通知 |
| <aop:aspect > |
定義切面 |
| <aop:aspectj-autoproxy > |
啟用@AspectJ註解驅動的切面 |
| <aop:before > |
定義AOP前置通知 |
| <aop:config > |
頂層的AOP配置元素。大多數的<aop:* >元素必須包含在其中 |
| <aop:declare-parents > |
為被通知的對象引入額外的介面,並透明的實現 |
| <aop:pointcut > |
定義切點 |
4、實現
假設有一個演員類Actor,演員類中有一個表演方法perform(),然後還有一個觀眾類Audience,這兩個類都在包com.example.springtest下,Audience類主要方法如下:
public class Audience{ //搬凳子 public void takeSeats(){} //歡呼 public void applaud(){} //計時,環繞通知需要一個ProceedingJoinPoint參數 public void timing(ProceedingJoinPoint joinPoint){ joinPoint.proceed(); } //演砸了 public void demandRefund(){} //測試帶參數 public void dealString(String word){} }
a、xml配置實現
??首先將Audience配置到springIOC中:
<bean id="audience" class="com.example.springtest.Audience"/>
然後申明通知:
<aop:config> <aop:aspect ref="audience"> <!-- 申明切點 --> <aop:pointcut id="performance" expression="execution(* com.example.springtest.Performer.perform(..))"/> <!-- 聲明傳遞參數切點 --> <aop:pointcut id="performanceStr" expression="execution(* com.example.springtest.Performer.performArg(String) and args(word))"/> <!-- 前置通知 --> <aop:before pointcut-ref="performance" method="takeSeats"/> <!-- 執行成功通知 --> <aop:after-returning pointcout-ref="performance" method="applaud"/> <!-- 執行異常通知 --> <aop:after-throwing pointcut-ref="performance" method="demandRefund"/> <!-- 環繞通知 --> <aop:around pointcut-ref="performance" method="timing"/> <!-- 傳遞參數 --> <aop:before pointcut-ref="performanceStr" arg-names="word" method="dealString"/> </aop:aspect></aop:config>
b、註解實現
直接在Audience類上加註解(Aspect註解並不能被spring自動探索並註冊,要麼寫到xml中,要麼使用@Aspectj註解或者加一個@Component註解),如下所示:
@Aspectpublic class Audience{ //定義切點 @Pointcut(execution(* com.example.springtest.Performer.perform(..))) public void perform(){} //定義帶參數切點 @Pointcut(execution(* com.example.springtest.Performer.performArg(String) and args(word))) public void performStr(String word){} //搬凳子 @Before("perform()") public void takeSeats(){} //歡呼 @AfterReturning("perform()") public void applaud(){} //計時,環繞通知需要一個ProceedingJoinPoint參數 @Around("perform()") public void timing(ProceedingJoinPoint joinPoint){ joinPoint.proceed(); } //演砸了 @AfterThrowing("perform()") public void demandRefund(){} //帶參數 @Before("performStr(word)") public void dealString(String word){}}
c、通過切面引入新功能
??既然可以用AOP為對象擁有的方法添加新功能,那為什麼不能為對象增加新的方法呢?利用被稱為引入的AOP概念,切面可以為Spring Bean添加新的方法,樣本圖如下:
當引入介面的方法被調用時,代理將此調用委託給實現了新介面的某個其他對象。實際上,Bean的實現被拆分到了多個類。
xml引入需要使用<aop:declare-parents >元素:
<aop:aspect> <aop:declare-parents types-matching="com.fxb.springtest.Performer+" implement-interface="com.fxb.springtest.AddTestInterface" default-impl="com.fxb.springtest.AddTestImpl"/></aop:aspect>
顧名思義<declare-parents>聲明了此切面所通知的Bean在它的對象階層中有了新的父類型。其中types-matching指定增強類;implement-interface指定實現新方法的介面;default-imple指定實現了implement-interface介面的實作類別,也可以用delegate-ref來指定一個Bean的引用。
註解引入,通過@DeclareParents註解
@DeclareParents(value="com.fxb.springtest.Performer+", defaultImpl=AddTestImpl.class)public static AddTestInterface addTestInterface;
同xml實現一樣,註解也由三部分組成:1、value屬性相當於tpes-matching屬性,標識被增強類;2、defaultImpl等同於default-imple,指定介面的實作類別;3、有@DeclareParents註解所標註的static屬性指定了將被引入的介面。
spring基礎(3:面向切面編程)