Spring(三)-AOP

来源:https://www.cnblogs.com/xiaoqigui/archive/2022/08/30/16639594.html
-Advertisement-
Play Games

1、名詞理解 切麵(Aspect): 含有前置通知,後置通知,返回通知,異常拋出通知,環繞通知等方法的類; 通知(Advice): 對原方法進行添加處理(如日誌等)的方法; 切入點(PointCute): 通知需要在哪些方法上執行的表達式;(可以唯一匹配或模糊匹配); 連接點(JoinPoint): ...


1、名詞理解

  • 切麵(Aspect):
    • 含有前置通知,後置通知,返回通知,異常拋出通知,環繞通知等方法的
  • 通知(Advice):
    • 對原方法進行添加處理(如日誌等)的方法
  • 切入點(PointCute):
    • 通知需要在哪些方法上執行的表達式;(可以唯一匹配或模糊匹配);
  • 連接點(JoinPoint):
    • 與切入點匹配的具體執行的方法
  • 目標(Target):
    • 原業務類(主要 是核心代碼);
  • 代理(Proxy):
    • 生成的代理類(包含原業務類的 核心代碼 和 通知裡面的代碼);

2、前置通知

2.1 jar

<properties>
	<spring.version>4.3.18.RELEASE</spring.version>
</properties>

<dependencies>
    <!-- spring-beans begin -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- spring-beans end -->

    <!-- spring-core begin -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- spring-core end -->

    <!-- spring-context begin -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- spring-context end -->

    <!-- spring-expression begin -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- spring-expression end -->

    <!-- spring-aspects begin -->
    <!-- maven項目中,使用aop的AspectJ框架,只需要增加此依賴,自動添加依賴aspectjweaver(包含了aspectjrt)-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- spring-aspects end -->

</dependencies>

2.2 切入點

通知需要在哪些方法上執行的表達式;(可以唯一匹配或模糊匹配);

2.2.1 唯一匹配

execution(public int com.kgc.spring.aspectj.ArithmeticCalculator.add(int ,int ))

execution(修飾符 返回值類型 方法全類名)

2.2.2 模糊匹配

execution(* com.kgc.spring.aspectj.*.*(..)

通用切入點表達式含義:

  • 第一個*:代表任意的修飾符,任意的返回值類型;

  • 第二個*:代表任意的類;

  • 第三個*:代表任意的方法;

  • . . :代表任意的類型和個數的形參;

2.2.3 可重用切入點表達式

其他地方直接應用此方法即可;

//重用切入點表達式
@Pointcut( "execution(* com.kgc.spring.aspectj.*.*(..))")
public void joinPointcut(){}

//同一個類中引用
@Before("joinPointcut()")
@After("joinPointcut()")

//其他類中引用(方法全類名)
@Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")

2.3 JoinPoint 和 ProceedingJoinPoint

2.3.1 JoinPoint 對象

JoinPoint對象封裝了SpringAop中切麵方法的信息,在切麵方法中添加JoinPoint參數,就可以獲取到封裝了該方法信息的JoinPoint對象。
常用api:

方法名 功能
Signature getSignature(); 獲取封裝了署名信息的對象,在該對象中可以獲取到目標方法名,所屬類的Class等信息
Object[] getArgs(); 獲取傳入目標方法的參數對象
Object getTarget(); 獲取被代理的對象
Object getThis(); 獲取代理對象

2.3.2 ProceedingJoinPoint對象

ProceedingJoinPoint對象是JoinPoint的子介面,該對象只用在@Around的切麵方法中 添加了 兩個方法.

方法名 功能
Object proceed() throws Throwable 執行目標方法
Object proceed(Object[] var1) throws Throwable 傳入的新的參數去執行目標方法

2.4 @Before

2.4.1 介面

ArithmeticCalculator

public interface ArithmeticCalculator {

    //加
    int add(int m,int n);

    //減
    int sub(int m,int n);

    //乘
    int nul(int m,int n);

    //除
    int div(int m,int n);

}

2.4.2 實現類

ArithmeticCalculatorImpl

@Service("arithmeticCalculator")  
//起別名,方便單元測試,根據別名,從容器中獲取
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int m, int n) {
        return m + n;
    }

    @Override
    public int sub(int m, int n) {
        return m - n;
    }

    @Override
    public int nul(int m, int n) {
        return m*n;
    }

    @Override
    public int div(int m, int n) {
        System.out.println("====== 執行 div 方法 ======");
        return m/n;
    }

}

2.4.3 @Before 前置通知

在目標方法執行前,自動執行此方法(通過代理實現);

@Component //聲明為一個普通的組件,放入spring的容器中,才可以生效
@Aspect  //聲明當前類是 一個切麵
public class LogAspect {

    //重用切入點表達式
    @Pointcut( "execution(* com.kgc.spring.aspectj.*.*(..))")
    public void joinPointcut(){}

    //前置通知 @Before
    @Before("joinPointcut()")
    public void  logBeforeMethod(JoinPoint joinPoint){

        //獲取通知作用的目標方法名
        String methodName = joinPoint.getSignature().getName();

        //獲取通知作用的目標方法入參,返回的是參數值數組
        Object[] methodParams = joinPoint.getArgs();

        System.out.println("------ LogAspect "+methodName+" 方法,入參:"+ Arrays.toString(methodParams) +" ------");
	}
    
}

2.5 配置文件

spring-aop.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 組件 -->
	<context:component-scan base-package="com.kgc.spring.aspectj"></context:component-scan>

    <!--  基於註解方式實現Aspect切麵  -->
    <!--  作用:當spring的容器檢測到此配置項,會自動將Aspect切麵匹配的目標對象,放入容器,預設使用的是jdk的動態代理  -->
	<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
    

</beans>

2.6測試

public  void  testSpringAopAspectj(){
    //從容器中獲取計算器的實例對象
    ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);

    System.out.println(arithmeticCalculator.getClass());

    //調用切麵作用的目標方法,執行操作,
    int result = arithmeticCalculator.div(20, 10);

    System.out.println("****** 通過單元測試,計算結果:"+result +" ******");

}

測試結果

class com.sun.proxy.$Proxy15
    
------ LogAspect div 方法,入參:[20, 10] ------
    
====== 執行 div 方法 ======
    
****** 通過單元測試,計算結果:2 ******    

3、後置通知

3.1 @After

目標方法發執行之後,自動執行;

特點:

  • 後置通知無法獲取目標方法的返回值;
  • 它的執行跟目標方法是否拋出異常無關,不影響此方法的執行;
@After("joinPointcut()")
public void  logAfterMethod(JoinPoint joinPoint){
    //獲取通知作用的目標方法名
    String methodName = joinPoint.getSignature().getName();

    //獲取通知作用的目標方法入參,返回的是參數值數組
    Object[] methodParams = joinPoint.getArgs();

    System.out.println("------ LogAspect "+methodName+" 方法執行結束 ------");
}

3.2 測試

3.2.1 無異常

@Test
public  void  testSpringAopAspectj(){
    //從容器中獲取計算器的實例對象
    ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);

    //調用切麵作用的目標方法,執行操作,
    int result = arithmeticCalculator.div(20, 10);

    System.out.println("****** 通過單元測試,計算結果:"+result +" ******");

}

測試結果

====== 執行 div 方法 ======
    
------ LogAspect div 方法執行結束 ------
    
****** 通過單元測試,計算結果:2 ******

3.2.2 有異常

@Test
public  void  testSpringAopAspectj(){
    //從容器中獲取計算器的實例對象
    ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);

    //調用切麵作用的目標方法,執行操作,
    int result = arithmeticCalculator.div(20, 0);

    System.out.println("****** 通過單元測試,計算結果:"+result +" ******");

}

測試結果

====== 執行 div 方法 ======
    
------ LogAspect div 方法執行結束 ------ //有異常也會執行後置通知

java.lang.ArithmeticException: / by zero

4、返回通知

4.1 @AfterReturning

  • 目標方法返回結果後自動執行,可以獲取目標方法的返回值;
  • 但是要求@AfterReturning必須增加屬性returning,指定一個參數名;
  • 且此參數名必須跟通知方法的一個形參名一致用於接收返回值;
@AfterReturning(value = "joinPointcut()",returning = "result")
public void  afterReturningMethod(JoinPoint joinPoint,Object result){

    //獲取通知作用的目標方法名
    String methodName = joinPoint.getSignature().getName();

    System.out.println("------ LogAspect "+methodName+" 方法,執行結果:"+ result +" ------");

}

4.2 測試

測試結果

====== 執行 div 方法 ======
    
------ LogAspect div 方法,返回結果:2 ------
    
****** 通過單元測試,計算結果:2 ******

5、異常拋出通知

5.1 @AfterThrowing

  • 異常拋出通知 @AfterThrowing ,在目標方法拋出異常後,可以獲取目標方法發生異常後拋出的異常信息;
  • 但是要求 @AfterThrowing 必須增加屬性 throwing,指定一個參數名;
  • 且此參數名必須跟通知方法的一個形參名一致,用於接收異常;
@AfterThrowing(value = "joinPointcut()",throwing = "ex")
public void logAfterThrowingMethod(JoinPoint joinPoint,Exception ex){

    //獲取通知作用的目標方法名
    String methodName = joinPoint.getSignature().getName();

    System.out.println("------ LogAspect "+methodName+" 方法,執行異常信息:"+ ex.getMessage() +" ------");

}

5.2 測試

@Test
public  void  testSpringAopAspectj(){
    //從容器中獲取計算器的實例對象
    ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);

    System.out.println(arithmeticCalculator.getClass());

    //調用切麵作用的目標方法,執行操作,
    int result = arithmeticCalculator.div(20, 0);

    System.out.println("****** 通過單元測試,計算結果:"+result +" ******");

}

測試結果

====== 執行 div 方法 ======

------ LogAspect div 方法,執行異常信息:/ by zero ------

java.lang.ArithmeticException: / by zero

6、環繞通知

6.1 @Around

  • 環繞通知 @Around,可以看作是上面四種通知的結合體,一般不建議跟單個的通知共用(防止衝突失效);
  • 作用:可以讓開發人員在環繞通知的處理方法中根據不同也業務邏輯,決定是否發起對目標方法的調用;
@Around(value = "joinPointcut()")
public Object logAroundMethod(ProceedingJoinPoint joinPoint){

    //獲取通知作用的目標方法名
    String methodName = joinPoint.getSignature().getName();

    //定義獲取目標方法的返回值變數
    Object result = null;

    try{
        //實現前置通知功能
        System.out.println("------ LogAspect "+methodName+" 方法 Around通知,入參:"+ Arrays.toString(joinPoint.getArgs()) +" ------");

        //手動調用原目標方法(業務中決定,是否對核心方法方法發起調用)
        result  = joinPoint.proceed();
    }catch (Throwable tx){
        //實現異常拋出通知功能
        System.out.println("------ LogAspect "+methodName+" 方法 Around通知,執行異常信息:"+ tx.getMessage() +" ------");

    }finally {
        //實現後置通知功能
        System.out.println("------ LogAspect "+methodName+" 方法 Around通知,執行結束 ------");
    }

    //實現返回通知功能
    System.out.println("------ LogAspect "+methodName+" 方法 Around通知,執行結果:"+ result +" ------");

    return result;
}

6.2 測試

6.2.1 測試結果,無異常

//調用切麵作用的目標方法,執行操作,
int result = arithmeticCalculator.div(20, 10);
class com.sun.proxy.$Proxy13
    
------ LogAspect div 方法 Around通知,入參:[20, 10] ------
    
====== 執行 div 方法 ======
    
------ LogAspect div 方法 Around通知,執行結束 ------
    
------ LogAspect div 方法 Around通知,返回結果:2 ------
    
****** 通過單元測試,計算結果:2 ******

6.2.2 測試結果,有異常

//調用切麵作用的目標方法,執行操作,
int result = arithmeticCalculator.div(20, 0);
class com.sun.proxy.$Proxy13
    
------ LogAspect div 方法 Around通知,入參:[20, 0] ------
    
====== 執行 div 方法 ======
    
------ LogAspect div 方法 Around通知,執行異常信息:/ by zero ------
    
------ LogAspect div 方法 Around通知,執行結束 ------
    
------ LogAspect div 方法 Around通知,返回結果:null ------

6.2.3 測試結果 不調用 原方法

//調用切麵作用的目標方法,執行操作,
int result = arithmeticCalculator.div(20, 0);

//(業務中決定,是否對核心方法發起調用)
//不調用核心方法
//result  = joinPoint.proceed();
------ LogAspect div 方法 Around通知,入參:[20, 10] ------
    
------ LogAspect div 方法 Around通知,執行結束 ------
    
------ LogAspect div 方法 Around通知,返回結果:null ------

7、切入點優先順序

當有多個前置通知時,我們想自定義前置通知順序:使用@Order(int)

指定切麵優先順序,一般都是int型整數,值越小優先順序越高**(預設值 2^31 - 1 最低優先順序);

7.1 多個前置通知

logBeforeMethod

@Before("joinPointcut()")
public void  logBeforeMethod(JoinPoint joinPoint){

    //獲取通知作用的目標方法名
    String methodName = joinPoint.getSignature().getName();

    //獲取通知作用的目標方法入參,返回的是參數值數組
    Object[] methodParams = joinPoint.getArgs();

    System.out.println("------ LogAspectBeforeMethod "+methodName+" 方法,入參:"+ Arrays.toString(methodParams) +" ------");

}

verifyBeforeMethod

@Component
@Aspect
public class VerifyParamAspect {
    @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()") 
    public void  verifyBeforeMethod( JoinPoint joinPoint){
        //獲取通知作用的目標方法名
        String methodName = joinPoint.getSignature().getName();

        //獲取通知作用的目標方法入參,返回的是參數值數組
        Object[] methodParams = joinPoint.getArgs();

        System.out.println("------ verifyBeforeMethod "+methodName+" 方法,入參:"+ Arrays.toString(methodParams) +" ------");

    }

}

7.2 測試(預設)

@Test
public   void testVerifyParamAspect(){

    //從容器中獲取計算器的實例對象
    ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);

    System.out.println(arithmeticCalculator.getClass());

    //調用切麵作用的目標方法,執行操作,
    int result = arithmeticCalculator.div(20, 10);

    System.out.println("****** 通過單元測試,計算結果:"+result +" ******");

}

測試結果

------ LogAspectBeforeMethod div 方法,入參:[20, 10] ------  //LogAspectBeforeMethod 先執行
    
------ verifyBeforeMethod div 方法,入參:[20, 10] ------
    
====== 執行 div 方法 ======
    
****** 通過單元測試,計算結果:2 ******

7.3 測試(自定義優先順序)

@Component
@Aspect
@Order(1) //指定切麵優先順序,一般都是int型整數,值越小,優先順序越高(預設值 2^31 - 1)
public class VerifyParamAspect {
    @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()") 
    public void  verifyBeforeMethod( JoinPoint joinPoint){
        //獲取通知作用的目標方法名
        String methodName = joinPoint.getSignature().getName();

        //獲取通知作用的目標方法入參,返回的是參數值數組
        Object[] methodParams = joinPoint.getArgs();

        System.out.println("------ verifyBeforeMethod "+methodName+" 方法,入參:"+ Arrays.toString(methodParams) +" ------");

    }

}

測試結果

------ verifyBeforeMethod div 方法,入參:[20, 10] ------ //優先順序高的切麵中的verifyBeforeMethod,先執行
    
------ LogAspectBeforeMethod div 方法,入參:[20, 10] ------
    
====== 執行 div 方法 ======
    
****** 通過單元測試,計算結果:2 ******

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題描述: 前端使用Get請求並且使用請求體傳遞參數,後端使用@RequestBody註解封裝參數,這時會出現400的異常信息。 解決方法: 1、Get請求不要使用請求體,使用請求體的話用POST請求。(建議,這樣才是正常的規範寫法) 2、保留Get請求與請求體,後端也可以用對象來封裝請求體中的參數 ...
  • 首先上結構 mynode -> app5 -> urls.py & views.py | -> templates -> 5 -> upload.html | -> mynode -> urls.py | -> media 按照順序,先上app5/urls.py from django.urls i ...
  • 眾所周知,Go lang的作用域相對嚴格,數據之間的通信往往要依靠參數的傳遞,但如果想在多個協程任務中間做數據通信,就需要通道(channel)的參與,我們可以把數據封裝成一個對象,然後把這個對象的指針傳入某個通道變數中,另外一個協程從這個通道中讀出變數的指針,並處理其指向的記憶體對象。 通道的聲明與 ...
  • 來源:https://segmentfault.com/a/1190000021109130 問題描述 前幾天在幫同事排查生產一個線上偶發的線程池錯誤 邏輯很簡單,線程池執行了一個帶結果的非同步任務。但是最近有偶發的報錯: java.util.concurrent.RejectedExecutionE ...
  • 如果說Go lang是靜態語言中的皇冠,那麼,Goroutine就是併發編程方式中的鑽石。Goroutine是Go語言設計體系中最核心的精華,它非常輕量,一個 Goroutine 只占幾 KB,並且這幾 KB 就足夠 Goroutine 運行完,這就能在有限的記憶體空間內支持大量 Goroutine協 ...
  • 原文連接:https://www.zhoubotong.site/post/78.html 開發中對於http請求是經常遇到,一般可能網路延遲或介面返回超時,對於發起客戶端的請求, 除了設置超時時間外,請求重試是很有必要考慮的,我們不用重覆造輪子,可以使用 https://github.com/ra ...
  • 哈嘍兄弟們 之前經常編寫Python腳本來進行數據處理、數據傳輸和模型訓練。隨著數據量和數據複雜性的增加,運行腳本可能需要一些時間。在等待數據處理完成時可以同時做一些其他工作。 為了達到這個目的,編寫了一組用於解決這個問題的Python腳本。使用這些腳本向手機發送流程更新、可視化和完成通知。當偶爾擁 ...
  • “一個空Object對象的占多大空間?” 一個工作了5年的Java程式員直接被搞蒙了。 大家好,我是Mic,一個工作了14年的Java程式員。 我把這個問題的文字版本整理到了15W字的面試文檔里,大家可以掃描文章尾端領取。 下麵看看高手的回答。 高手: 在開啟了壓縮指針的情況下,Object預設會占 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...