spring基礎(3:面向切面編程)
一.面向切面編程
??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)
??切點定義了通知所要織入的一個或多個連接點。如果說通知定義了切面的“什麽
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註解),如下所示:
@Aspect
public 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:面向切面編程)