1. 程式人生 > 實用技巧 >Spring - 4 AOP

Spring - 4 AOP

Spring - AOP

1)AOP簡介

1.1)OOP開發思路

1.2)AOP開發思想

1.3)AOP概念

  • AOP(Aspect Oriented Programing)面向切面程式設計,一種程式設計正規化,隸屬於軟工範疇,指導開發者如何組織程式結構

  • AOP彌補了OOP的不足,基於OOP基礎之上進行橫向開發

    • uOOP規定程式開發以類為主體模型,一切圍繞物件進行,完成某個任務先構建模型

    • uAOP程式開發主要關注基於OOP開發中的共性功能,一切圍繞共性功能進行,完成某個任務先構建可能遇到的所有共性功能(當所有功能都開發出來也就沒有共性與非共性之分)

  • “AOP聯盟”

1.4)AOP作用

  • 伴隨著AOP時代的降臨,可以從各個行業的標準化、規範化開始入手,一步一步將所有共性功能逐一開發完畢,最終以功能組合來完成個別業務模組乃至整體業務系統的開發

  • 目標:將軟體開發由手工製作走向半自動化/全自動化階段,實現“插拔式元件體系結構”搭建

1.5)AOP優勢

  • 提高程式碼的可重用性

  • 業務程式碼編碼更簡潔

  • 業務程式碼維護更高效

  • 業務功能擴充套件更便捷

2)AOP入門案例

2.1)AOP相關概念

  • Joinpoint(連線點):就是方法

  • Pointcut(切入點):就是挖掉共性功能的方法

  • Advice(通知):就是共性功能,最終以一個方法的形式呈現

  • Aspect(切面):就是共性功能與挖的位置的對應關係

  • Target(目標物件):就是挖掉功能的方法對應的類產生的物件,這種物件是無法直接完成最終工作的

  • Weaving(織入):就是將挖掉的功能回填的動態過程

  • Proxy(代理):目標物件無法直接完成工作,需要對其進行功能回填,通過建立原始物件的代理物件實現

  • Introduction(引入/引介) :就是對原始物件無中生有的新增成員變數或成員方法

2.2)AOP開發過程

  • 開發階段(開發者完成)

    • 正常的製作程式

    • 將非共性功能開發到對應的目標物件類中,並製作成切入點方法

    • 將共性功能獨立開發出來,製作成通知

    • 在配置檔案中,宣告切入點

    • 在配置檔案中,宣告切入點通知間的關係(含通知型別),即切面

  • 執行階段(AOP完成)

    • Spring容器載入配置檔案,監控所有配置的切入點方法的執行

    • 當監控到切入點方法被執行,使用代理機制,動態建立目標物件代理物件,根據通知類別,在代理物件的對應位置將通知對應的功能織入,完成完整的程式碼邏輯並執行

2.2)AOP開發方式

  • XML方式

  • XML+註解方式

  • 註解方式

2.3)入門案例製作分析

1.匯入相關座標

2.確認要抽取的功能,並將其製作成方法儲存到專用的類中,刪除原始業務中對應的功能

3.將所有進行AOP操作的資源載入到IoC容器中

4.使用配置的方式描述被抽取功能的位置,並描述被抽取功能與對應位置的關係

5.執行程式

步驟一 匯入座標

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

步驟二 在業務層抽取通用程式碼

步驟三 把通知加入spring容器管理

步驟四 在配置檔案中配置aop的配置

<!--aop配置-->
<aop:config>
    <!--配置切入點-->
    <aop:pointcut id="pt" expression="execution(* *..*())"/>
    <!--配置切面-->
    <aop:aspect ref="myAdvice">
        <!—通知與切入點之間的關係-->
        <aop:before method="logAdvice" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

3)AOP配置(XML)

3.1)AspectJ

  • Aspect(切面)用於描述切入點與通知間的關係,是AOP程式設計中的一個概念

  • AspectJ是基於java語言對Aspect的實現

3.2)AOP配置

3.2.1)aop:config

  • 名稱:aop:config

  • 型別:標籤

  • 歸屬:beans標籤

  • 作用:設定AOP

  • 格式:

    <beans>
        <aop:config>……</aop:config>
        <aop:config>……</aop:config>
    </beans>
    
  • 說明:一個beans標籤中可以配置多個aop:config標籤

3.2.2)aop:aspect

  • 名稱:aop:aspect

  • 型別:標籤

  • 歸屬:aop:config標籤

  • 作用:設定具體的AOP通知對應的切入點

  • 格式:

    <aop:config>
        <aop:aspect ref="beanId">……</aop:aspect>
        <aop:aspect ref="beanId">……</aop:aspect>
    </aop:config>
    
  • 說明:

    一個aop:config標籤中可以配置多個aop:aspect標籤

  • 基本屬性:

    • ref :通知所在的bean的id

3.2.3)aop:pointcut

  • 名稱:aop:pointcut

  • 型別:標籤

  • 歸屬:aop:config標籤、aop:aspect標籤

  • 作用:設定切入點

  • 格式:

    <aop:config>
        <aop:pointcut id="pointcutId" expression="……"/>
        <aop:aspect>
            <aop:pointcut id="pointcutId" expression="……"/>
        </aop:aspect>
    </aop:config>
    
  • 說明:

    一個aop:config標籤中可以配置多個aop:pointcut標籤,且該標籤可以配置在aop:aspect標籤內

  • 基本屬性:

    • id :識別切入點的名稱

    • expression :切入點表示式

3.3)切入點

  • 切入點描述的是某個方法

  • 切入點表示式是一個快速匹配方法描述的通配格式,類似於正則表示式

3.4)切入點表示式的組成

  • 切入點描述的是某個方法

  • 切入點表示式是一個快速匹配方法描述的通配格式,類似於正則表示式

    關鍵字(訪問修飾符  返回值  包名.類名.方法名(引數)異常名)
    

​ 關鍵字:描述表示式的匹配模式(參看關鍵字列表)

​ 訪問修飾符:方法的訪問控制權限修飾符

​ 類名:方法所在的類(此處可以配置介面名稱)

​ 異常:方法定義中指定丟擲的異常

  • 範例:

    execution(public User com.itheima.service.UserService.findById(int))
    

3.4.1)切入點表示式——關鍵字

  • execution :匹配執行指定方法

  • args :匹配帶有指定引數型別的方法

  • within :……

  • this :……

  • target :……

  • @within :……

  • @target :……

  • @args :……

  • @annotation :……

  • bean :……

  • reference pointcut :……

3.4.2)切入點表示式——萬用字元

  • *:單個獨立的任意符號,可以獨立出現,也可以作為字首或者字尾的匹配符出現

    execution(public * com.itheima.*.UserService.find*(*))
    

​ 匹配com.itheima包下的任意包中的UserService類或介面中所有find開頭的帶有一個引數的方法

  • .. :多個連續的任意符號,可以獨立出現,常用於簡化包名與引數的書寫

    execution(public User com..UserService.findById(..))
    
    

​ 匹配com包下的任意包中的UserService類或介面中所有名稱為findById的方法

  • +:專用於匹配子類型別

    execution(* *..*Service+.*(..))
    

3.4.3)切入點表示式——邏輯運算子

  • && :連線兩個切入點表示式,表示兩個切入點表示式同時成立的匹配

  • || :連線兩個切入點表示式,表示兩個切入點表示式成立任意一個的匹配

  • ! :連線單個切入點表示式,表示該切入點表示式不成立的匹配

3.4.4)切入點表示式——範例

execution(* *(..))
execution(* *..*(..))
execution(* *..*.*(..))
execution(public * *..*.*(..))
execution(public int *..*.*(..))
execution(public void *..*.*(..))
execution(public void com..*.*(..)) 
execution(public void com..service.*.*(..))
execution(public void com.itheima.service.*.*(..))
execution(public void com.itheima.service.User*.*(..))
execution(public void com.itheima.service.*Service.*(..))
execution(public void com.itheima.service.UserService.*(..))
execution(public User com.itheima.service.UserService.find*(..))
execution(public User com.itheima.service.UserService.*Id(..))
execution(public User com.itheima.service.UserService.findById(..))
execution(public User com.itheima.service.UserService.findById(int))
execution(public User com.itheima.service.UserService.findById(int,int))
execution(public User com.itheima.service.UserService.findById(int,*))
execution(public User com.itheima.service.UserService.findById(*,int))
execution(public User com.itheima.service.UserService.findById())
execution(List com.itheima.service.*Service+.findAll(..))

3.5)切入點的三種配置方式

<aop:config>
    <!--配置公共切入點-->
    <aop:pointcut id="pt1" expression="execution(* *(..))"/>
    <aop:aspect ref="myAdvice">
        <!--配置區域性切入點-->
        <aop:pointcut id="pt2" expression="execution(* *(..))"/>
        <!--引用公共切入點-->
        <aop:before method="logAdvice" pointcut-ref="pt1"/>
        <!--引用區域性切入點-->
        <aop:before method="logAdvice" pointcut-ref="pt2"/>
        <!--直接配置切入點-->
        <aop:before method="logAdvice" pointcut="execution(* *(..))"/>
    </aop:aspect>
</aop:config>

3.6)切入點配置經驗

  • 企業開發命名規範嚴格遵循規範文件進行

  • 先為方法配置區域性切入點

  • 再抽取類中公共切入點

  • 最後抽取全域性切入點

  • 程式碼走查過程中檢測切入點是否存在越界性包含

  • 程式碼走查過程中檢測切入點是否存在非包含性進駐

  • 設定AOP執行檢測程式,在單元測試中監控通知被執行次數與預計次數是否匹配

  • 設定完畢的切入點如果發生調整務必進行迴歸測試

(以上規則適用於XML配置格式)

3.7)通知型別

AOP的通知型別共5種

  • 前置通知:原始方法執行前執行,如果通知中丟擲異常,阻止原始方法執行

    應用:資料校驗

  • 後置通知:原始方法執行後執行,無論原始方法中是否出現異常,都將執行通知

    應用:現場清理

  • 返回後通知:原始方法正常執行完畢並返回結果後執行,如果原始方法中丟擲異常,無法執行

    應用:返回值相關資料處理

  • 丟擲異常後通知:原始方法丟擲異常後執行,如果原始方法沒有丟擲異常,無法執行

    應用:對原始方法中出現的異常資訊進行處理

  • 環繞通知:在原始方法執行前後均有對應執行執行,還可以阻止原始方法的執行

    應用:十分強大,可以做任何事情

3.7.1)aop:before

  • 名稱:aop:before

  • 型別:標籤

  • 歸屬:aop:aspect標籤

  • 作用:設定前置通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:before method="methodName" pointcut="……"/>
    </aop:aspect>
    
    
  • 說明:一個aop:aspect標籤中可以配置多個aop:before標籤

  • 基本屬性:

    • method :在通知類中設定當前通知類別對應的方法

    • pointcut :設定當前通知對應的切入點表示式,與pointcut-ref屬性衝突

    • pointcut-ref :設定當前通知對應的切入點id,與pointcut屬性衝突

3.7.2)aop:after

  • 名稱:aop:after

  • 型別:標籤

  • 歸屬:aop:aspect標籤

  • 作用:設定後置通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:after method="methodName" pointcut="……"/>
    </aop:aspect>
    
    
  • 說明:一個aop:aspect標籤中可以配置多個aop:after標籤

  • 基本屬性:

    • method :在通知類中設定當前通知類別對應的方法

    • pointcut :設定當前通知對應的切入點表示式,與pointcut-ref屬性衝突

    • pointcut-ref :設定當前通知對應的切入點id,與pointcut屬性衝突

3.7.3)aop:after-returning

  • 名稱:aop:after-returning

  • 型別:標籤

  • 歸屬:aop:aspect標籤

  • 作用:設定返回後通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:after-returning method="methodName" pointcut="……"/>
    </aop:aspect>
    
    
  • 說明:一個aop:aspect標籤中可以配置多個aop:after-returning標籤

  • 基本屬性:

    • method :在通知類中設定當前通知類別對應的方法

    • pointcut :設定當前通知對應的切入點表示式,與pointcut-ref屬性衝突

    • pointcut-ref :設定當前通知對應的切入點id,與pointcut屬性衝突

3.7.4)aop:after-throwing

  • 名稱:aop:after-throwing

  • 型別:標籤

  • 歸屬:aop:aspect標籤

  • 作用:設定丟擲異常後通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:after-throwing method="methodName" pointcut="……"/>
    </aop:aspect>
    
    
  • 說明:一個aop:aspect標籤中可以配置多個aop:after-throwing標籤

  • 基本屬性:

    • method :在通知類中設定當前通知類別對應的方法

    • pointcut :設定當前通知對應的切入點表示式,與pointcut-ref屬性衝突

    • pointcut-ref :設定當前通知對應的切入點id,與pointcut屬性衝突

3.7.5)aop:around

  • 名稱:aop:around

  • 型別:標籤

  • 歸屬:aop:aspect標籤

  • 作用:設定環繞通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:around method="methodName" pointcut="……"/>
    </aop:aspect>
    
    
  • 說明:一個aop:aspect標籤中可以配置多個aop:around標籤

  • 基本屬性:

    • method :在通知類中設定當前通知類別對應的方法

    • pointcut :設定當前通知對應的切入點表示式,與pointcut-ref屬性衝突

    • pointcut-ref :設定當前通知對應的切入點id,與pointcut屬性衝突

環繞通知的開發方式

  • 環繞通知是在原始方法的前後新增功能,在環繞通知中,存在對原始方法的顯式呼叫

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();
        return ret;
    }
    
    
  • 環繞通知方法相關說明:

    • 方法須設定Object型別的返回值,否則會攔截原始方法的返回。如果原始方法返回值型別為void,通知方 也可以設定返回值型別為void,最終返回null

    • 方法需在第一個引數位置設定ProceedingJoinPoint物件,通過該物件呼叫proceed()方法,實現對原始方法的呼叫。如省略該引數,原始方法將無法執行

    • 使用proceed()方法呼叫原始方法時,因無法預知原始方法執行過程中是否會出現異常,強制丟擲Throwable物件,封裝原始方法中可能出現的異常資訊

3.8)通知順序(瞭解)

當同一個切入點配置了多個通知時,通知會存在執行的先後順序,該順序以通知配置的順序為準

3.9)通知獲取資料

  • 引數

  • 返回值

  • 異常

3.9.1)通知獲取引數資料

第一種情況:

  • 設定通知方法第一個引數為JoinPoint,通過該物件呼叫getArgs()方法,獲取原始方法執行的引數陣列

    public void before(JoinPoint jp) throws Throwable {
        Object[] args = jp.getArgs();
    }
    
  • 所有的通知均可以獲取引數

第二種情況:

  • 設定切入點表示式為通知方法傳遞引數(鎖定通知變數名)

  • 原始方法

第三種情況

  • 設定切入點表示式為通知方法傳遞引數(改變通知變數名的定義順序)

  • 原始方法

3.9.2)通知獲取返回值資料

第一種:返回值變數名

  • 設定返回值變數名

  • 原始方法

    public int save() {
    	System.out.println("user service running...");
        return 100;
    }
    
    
  • AOP配置

    <aop:aspect ref="myAdvice">
        <aop:pointcut id="pt3" expression="execution(* *(..))  "/>
        <aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/>
    </aop:aspect>
    
    
  • 通知類

    public void afterReturning(Object ret) {
        System.out.println(ret);
    }
    
    
  • 適用於返回後通知(after-returning)

第二種:

  • 在通知類的方法中呼叫原始方法獲取返回值

  • 原始方法

    public int save() {
        System.out.println("user service running...");
        return 100;
    }
    
    
  • AOP配置l

    <aop:aspect ref="myAdvice">
        <aop:pointcut id="pt2" expression="execution(* *(..))  "/>
        <aop:around method="around" pointcut-ref="pt2" />
    </aop:aspect>
    
    
  • 通知類

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();
        return ret;
    }
    
    
  • 適用於環繞通知(around)

3.9.3)通知獲取異常資料

第一種:通知類的方法中呼叫原始方法捕獲異常

  • 在通知類的方法中呼叫原始方法捕獲異常

  • 原始方法

    public void save() {
        System.out.println("user service running...");
        int i = 1/0;
    }
    
    
  • AOP配置

    <aop:aspect ref="myAdvice">
        <aop:pointcut id="pt4" expression="execution(* *(..))  "/>
        <aop:around method="around" pointcut-ref="pt4" />
    </aop:aspect>
    
    
  • 通知類

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();	//對此處呼叫進行try……catch……捕獲異常,或丟擲異常
        return ret;
    }
    
    
  • 適用於環繞通知(around)

第二種:

  • 設定異常物件變數名

  • 原始方法

    public void save() {
        System.out.println("user service running...");
        int i = 1/0;
    }
    
    
  • AOP配置

    <aop:aspect ref="myAdvice">
    	<aop:pointcut id="pt4" expression="execution(* *(..))  "/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pt4" throwing="t"/>
    </aop:aspect>
    
    
  • 通知類

    public void afterThrowing(Throwable t){
        System.out.println(t.getMessage());
    }
    
  • 適用於返回後通知(after-throwing)

4)AOP配置(註解)

4.1)AOP配置

4.2)註解開發AOP製作步驟

在XML格式基礎上

  • 匯入座標(伴隨spring-context座標匯入已經依賴匯入完成)

  • 開啟AOP註解支援

  • 配置切面@Aspect

  • 定義專用的切入點方法,並配置切入點@Pointcut

  • 為通知方法配置通知型別及對應切入點@Before

4.3)註解開發AOP注意事項

1.切入點最終體現為一個方法,無參無返回值,無實際方法體內容,但不能是抽象方法

2.引用切入點時必須使用方法呼叫名稱,方法後面的()不能省略

3.切面類中定義的切入點只能在當前類中使用,如果想引用其他類中定義的切入點使用“類名.方法名()”引用

4.可以在通知型別註解後新增引數,實現XML配置中的屬性,例如after-returning後的returning屬性

4.4)AOP註解詳解

4.4.1)@Aspect

  • 名稱:@Aspect

  • 型別:註解

  • 位置:類定義上方

  • 作用:設定當前類為切面類

  • 格式:

    @Aspect
    public class AopAdvice {
    }
    
    
  • 說明:一個beans標籤中可以配置多個aop:config標籤

4.4.2)@Pointcut

  • 名稱:@Pointcut

  • 型別:註解

  • 位置:方法定義上方

  • 作用:使用當前方法名作為切入點引用名稱

  • 格式:

    @Pointcut("execution(* *(..))")
    public void pt() {
    }
    
    
  • 說明:被修飾的方法忽略其業務功能,格式設定為無參無返回值的方法,方法體內空實現(非抽象)

4.4.3)@Before

  • 名稱:@Before

  • 型別:註解

  • 位置:方法定義上方

  • 作用:標註當前方法作為前置通知

  • 格式:

    @Before("pt()")
    public void before(){
    }
    
    
  • 特殊引數:

4.4.4)@After

  • 名稱:@After

  • 型別:註解

  • 位置:方法定義上方

  • 作用:標註當前方法作為後置通知

  • 格式:

    @After("pt()")
    public void after(){
    }
    
    
  • 特殊引數:

4.4.5)@AfterReturning

  • 名稱:@AfterReturning

  • 型別:註解

  • 位置:方法定義上方

  • 作用:標註當前方法作為返回後通知

  • 格式:

    @AfterReturning(value="pt()",returning = "ret")
    public void afterReturning(Object ret) {
    }
    
    
  • 特殊引數:

    • returning :設定使用通知方法引數接收返回值的變數名

4.4.6)@AfterThrowing

  • 名稱:@AfterThrowing

  • 型別:註解

  • 位置:方法定義上方

  • 作用:標註當前方法作為異常後通知

  • 格式:

    @AfterThrowing(value="pt()",throwing = "t")
    public void afterThrowing(Throwable t){
    }
    
  • 特殊引數:

    • throwing :設定使用通知方法引數接收原始方法中丟擲的異常物件名

4.4.7)@Around

  • 名稱:@Around

  • 型別:註解

  • 位置:方法定義上方

  • 作用:標註當前方法作為環繞通知

  • 格式:

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();
        return ret;
    }
    
    
  • 特殊引數:

4.5)AOP註解開發通知執行順序控制(瞭解)

1.AOP使用XML配置情況下,通知的執行順序由配置順序決定,在註解情況下由於不存在配置順序的概念的概念,參照通知所配置的方法名字串對應的編碼值順序,可以簡單理解為字母排序

  • 同一個通知類中,相同通知型別以方法名排序為準

  • 不同通知類中,以類名排序為準

  • 使用@Order註解通過變更bean的載入順序改變通知的載入順序

2.企業開發經驗

  • 通知方法名由3部分組成,分別是字首、順序編碼、功能描述

  • 字首為固定字串,例如baidu、itheima等,無實際意義

  • 順序編碼為6位以內的整數,通常3位即可,不足位補0

  • 功能描述為該方法對應的實際通知功能,例如exception、strLenCheck

    • 制通知執行順序使用順序編碼控制,使用時做一定空間預留

    • 003使用,006使用,預留001、002、004、005、007、008

    • 使用時從中段開始使用,方便後期做前置追加或後置追加

    • 最終順序以執行順序為準,以測試結果為準,不以設定規則為準

4.6)AOP註解驅動

  • 名稱:@EnableAspectJAutoProxy

  • 型別:註解

  • 位置:Spring註解配置類定義上方

  • 作用:設定當前類開啟AOP註解驅動的支援,載入AOP註解

  • 格式:

    @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }