SpringBoot AOP切面實現

文章目錄

  • 一、AOP簡介
  • 二、AOP體系與概念
  • 三、AOP實例
    • 1、創建SpringBoot工程
    • 2、添加依賴
    • 3、AOP相關注解
      • 3.1、@Aspect
      • 3.2、@Pointcut
        • 3.2.1、execution()
        • 3.2.2、annotation()
      • 3.3、@Around
      • 3.4、@Before
      • 3.5、@After
      • 3.6、@AfterReturning
      • 3.7、@AfterThrowing

一、AOP簡介

AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(其余兩個:IOC - 控制反轉DI - 依賴注入)。


那么AOP為何那么重要呢?

在我們的程序中,經常存在一些系統性的需求,比如 權限校驗日志記錄統計等,這些代碼會散落穿插在各個業務邏輯中,非常冗余且不利于維護,那么面向切面編程往往讓我們的開發更加低耦合,也大大減少了代碼量,同時呢讓我們更專注于業務模塊的開發,把那些與業務無關的東西提取出去,便于后期的維護和迭代。


二、AOP體系與概念

簡單地去理解,其實AOP要做三類事:

  • 在哪里切入,也就是權限校驗等非業務操作在哪些業務代碼中執行。

  • 在什么時候切入,是業務代碼執行前還是執行后。

  • 切入后做什么事,比如做權限校驗、日志記錄等。

AOP的體系圖:
請添加圖片描述
一些概念:

概念說明
Pointcut切點,決定處理如權限校驗、日志記錄等在何處切入業務代碼中(即織入切面)。切點分為execution方式和 annotation 方式。前者可以用路徑表達式指定哪些類織入切面,后者可以指定被哪些注解修飾的代碼織入切面。
Advice處理,包括處理時機和處理內容。處理內容就是要做什么事,比如校驗權限和記錄日志。處理時機就是在什么時機執行處理內容,分為前置處理(即業務代碼執行前)、后置處理(業務代碼執行后)等。
Aspect切面,即 PointcutAdvice
Joint point連接點,是程序執行的一個點。例如,一個方法的執行或者一個異常的處理。在 Spring AOP 中,一個連接點總是代表一個方法執行。
Weaving織入,就是通過動態代理,在目標對象方法中執行處理內容的過程。

三、AOP實例

1、創建SpringBoot工程

如何創建詳見:IDEA 創建 SpringBoot 項目


2、添加依賴

<!-- springboot-aop包,AOP切面注解,Aspectd等相關注解 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3、AOP相關注解

package com.cw.tsb.app.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class ControllerAspect {@Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}@Around("pointCut()")public Object doAround(ProceedingJoinPoint joinPoint) {System.out.println("------------- doAround.");Object obj = null;try {obj = joinPoint.proceed();} catch (Throwable t){t.printStackTrace();}return obj;}@After("pointCut()")public void doAfter(JoinPoint joinPoint){System.out.println("------------- doAfter.");}@Before("pointCut()")public void doBefore(JoinPoint joinPoint){System.out.println("------------- doBefore.");}/*** 后置返回*      如果第一個參數為JoinPoint,則第二個參數為返回值的信息*      如果第一個參數不為JoinPoint,則第一個參數為returning中對應的參數* returning:限定了只有目標方法返回值與通知方法參數類型匹配時才能執行后置返回通知,否則不執行,*      參數為Object類型將匹配任何目標返回值*/@AfterReturning(value = "pointCut()", returning = "result")public void doAfterReturning(JoinPoint joinPoint, String result){System.out.println("doAfterReturning result = " + result);}@AfterThrowing(value = "pointCut()", throwing = "t")public void doAfterThrowing(JoinPoint joinPoint, Throwable t){System.out.println("------------- doAfterThrowing throwable = " + t.toString());}
}

3.1、@Aspect

該注解要添加在類上,聲明這是一個切面類,使用時需要與@Component注解一起用,表明同時將該類交給spring管理。

@Component
@Aspect
public class ControllerAspect {
}

3.2、@Pointcut

用來定義一個切點,即上文中所關注的某件事情的入口,切入點定義了事件觸發時機。

該注解需要添加在方法上,該方法簽名必須是 public void 類型,可以將@Pointcut 中的方法看作是一個用來引用的助記符,因為表達式不直觀,因此我們可以通過方法簽名的方式為此表達式命名。因此 @Pointcut 中的方法只需要方法簽名,而不需要在方法體內編寫實際代碼

該注解有兩個常用的表達式:execution()annotation()


3.2.1、execution()

@Aspect
@Component
public class ControllerAspect {@Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}
}

表達式為:

execution(* com.cw.tsb.app.controller..*.*(..))
  • 第一個 * :表示返回值類型,* 表示所有類型;

  • 包名:標識需要攔截的包名;

  • 包名后的 ..:表示當前包和當前包的所有子包,在本例中指 com.cw.tsb.app.controller 包、子包下所有類;

  • 第二個 * :表示類名,* 表示所有類;

  • 最后的 *(..) :星號表示方法名,* 表示所有的方法,后面括弧里面表示方法的參數,兩個句點表示任何參數。


3.2.2、annotation()

annotation() 方式是針對某個注解來定義切點,比如我們對具有 @PostMapping 注解的方法做切面,可以如下定義切面:

@Aspect
@Component
public class ControllerAspect {@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}
}

然后使用該切面的話,就會切入注解是 @PostMapping 的所有方法。這種方式很適合處理 @GetMapping@PostMapping@DeleteMapping不同注解有各種特定處理邏輯的場景。

還有就是如上面案例所示,針對自定義注解來定義切面。

@Aspect
@Component
public class ControllerAspect {@Pointcut("@annotation(com.cw.tsb.app.annotation.PermissionsAnnotation)")private void permissionCheck() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}
}

3.3、@Around

@Around 注解用于修飾 Around 增強處理,Around增強處理非常強大,表現在:

  • @Around 可以自由選擇增強動作與目標方法的執行順序,也就是說可以在增強動作前后,甚至過程中執行目標方法。這個特性的實現在于,調用 ProceedingJoinPoint 參數的 procedd() 方法才會執行目標方法。

  • @Around 可以改變執行目標方法的參數值,也可以改變執行目標方法之后的返回值。

Around 增強處理有以下特點:

  • 當定義一個 Around 增強處理方法時,該方法的第一個形參必須是 ProceedingJoinPoint 類型(至少一個形參)。在增強處理方法體內,調用 ProceedingJoinPointproceed 方法才會執行目標方法:這就是 @Around 增強處理可以完全控制目標方法執行時機、如何執行的關鍵;如果程序沒有調用 ProceedingJoinPointproceed 方法,則目標方法不會執行。

  • 調用 ProceedingJoinPointproceed 方法時,還可以傳入一個 Object[] 對象,該數組中的值將被傳入目標方法作為實參 —— 這就是 Around 增強處理方法可以改變目標方法參數值的關鍵。這就是如果傳入的 Object[] 數組長度與目標方法所需要的參數個數不相等,或者 Object[] 數組元素與目標方法所需參數的類型不匹配,程序就會出現異常。

@Around 功能雖然強大,但通常需要在線程安全的環境下使用。因此,如果使用普通的@Before@AfterReturning 就能解決的問題,就沒有必要使用 Around 了。如果需要目標方法執行之前和之后共享某種狀態數據,則應該考慮使用 Around 。尤其是需要使用增強處理阻止目標的執行,或需要改變目標方法的返回值時,則只能使用 Around 增強處理了。


3.4、@Before

@Before 注解指定的方法在切面切入目標方法之前執行,可以做一些 Log 處理,也可以做一些信息的統計,比如 獲取用戶的請求 URL 以及 用戶的 IP 地址等等,這個在做個人站點的時候都能用得到,都是常用的方法。例如下面代碼:

@Aspect
@Component
public class ControllerAspect {@Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}/*** 在上面定義的切面方法之前執行該方法* @param joinPoint jointPoint*/@Before("pointCut()")public void doBefore(JoinPoint joinPoint) {// 獲取簽名Signature signature = joinPoint.getSignature();// 獲取切入的包名String declaringTypeName = signature.getDeclaringTypeName();// 獲取即將執行的方法名String funcName = signature.getName();log.info("即將執行方法為: {},屬于{}包", funcName, declaringTypeName);// 也可以用來記錄一些信息,比如獲取請求的 URL 和 IPServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 獲取請求 URLString url = request.getRequestURL().toString();// 獲取請求 IPString ip = request.getRemoteAddr();}
}

JointPoint 對象很有用,可以用它來獲取一個簽名,利用簽名可以獲取請求的包名、方法名,包括參數(通過 joinPoint.getArgs() 獲取)等。


3.5、@After

@After 注解和 @Before 注解相對應,指定的方法在切面切入目標方法之后執行,也可以做一些完成某方法之后的 Log 處理。

@Aspect
@Component
public class ControllerAspect {@Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}/*** 在上面定義的切面方法之后執行該方法* @param joinPoint jointPoint*/@After("pointCut()")public void doAfter(JoinPoint joinPoint) {log.info("==== doAfter 方法進入了====");Signature signature = joinPoint.getSignature();String method = signature.getName();log.info("方法{}已經執行完", method);}
}

到這里,我們來寫個 Controller 測試一下執行結果,新建一個 AopController 如下:

@RestController
@RequestMapping("/aop")
public class AopController {@GetMapping("/{name}")public String testAop(@PathVariable String name) {return "Hello " + name;}
}

啟動項目,在瀏覽器中輸入:http://localhost:8080/aop/csdn,觀察一下控制臺的輸出信息:

====doBefore 方法進入了====  
即將執行方法為: testAop,屬于com.itcodai.mutest.AopController包  
用戶請求的 url 為:http://localhost:8080/aop/name,ip地址為:0:0:0:0:0:0:0:1  
==== doAfter 方法進入了====  
方法 testAop 已經執行完

從打印出來的 Log 中可以看出程序執行的邏輯與順序,可以很直觀的掌握 @Before@After 兩個注解的實際作用。


3.6、@AfterReturning

@AfterReturning 注解和 @After 有些類似,區別在于 @AfterReturning 注解可以用來捕獲切入方法執行完之后的返回值,對返回值進行業務邏輯上的增強處理,例如:

@Aspect
@Component
public class ControllerAspect {@Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}/*** 后置返回*      如果第一個參數為JoinPoint,則第二個參數為返回值的信息*      如果第一個參數不為JoinPoint,則第一個參數為returning中對應的參數* returning:限定了只有目標方法返回值與通知方法參數類型匹配時才能執行后置返回通知,否則不執行,*      參數為Object類型將匹配任何目標返回值*/@AfterReturning(value = "pointCut()", returning = "result")public void doAfterReturning(JoinPoint joinPoint, String result){// 實際項目中可以根據業務做具體的返回值增強}
}

需要注意的是,在 @AfterReturning 注解 中,屬性 returning 的值必須要和參數保持一致,否則會檢測不到。該方法中的第二個入參就是被切方法的返回值,在 doAfterReturning 方法中可以對返回值進行增強,可以根據業務需要做相應的封裝。


3.7、@AfterThrowing

當被切方法執行過程中拋出異常時,會進入 @AfterThrowing 注解的方法中執行,在該方法中可以做一些異常的處理邏輯。要注意的是 throwing 屬性的值必須要和參數一致,否則會報錯。該方法中的第二個入參即為拋出的異常。

@Aspect
@Component
public class ControllerAspect {@Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//該方法僅用于掃描controller包下類中的方法,而不做任何特殊的處理。}@AfterThrowing(value = "pointCut()", throwing = "t")public void doAfterThrowing(JoinPoint joinPoint, Throwable t){System.out.println("------------- doAfterThrowing throwable = " + t.toString());// 處理異常的邏輯}
}



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446667.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446667.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446667.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

英語口語-文章朗讀Week8 Friday

文章 It is a phenomenon that people are losing trust in each other in today’s society. Some people become selfish,and for interest, they are likely to betray their colleagues,friends, and even their relatives. They tend to cater to those who can benefit …

C++primer 第 3 章 字符串、向量和數組 3 . 4 迭代器介紹

3.4迭代器介紹 我們已經知道可以使用下標運算符來訪問string對象的字符或vector對象的元素&#xff0c;還有另外一種更通用的機制也可以實現同樣的目的&#xff0c;這就是迭代器&#xff08;iterator&#xff09;。在第II部分中將要介紹&#xff0c;除了vector之外&#xff0c…

ClickHouse 函數

文章目錄一、日期函數1、時間或日期截取函數&#xff08;返回非日期&#xff09;2、時間或日期截取函數&#xff08;返回日期&#xff09;3、日期或時間日期生成函數二、類型轉化類函數1、精度保留&#xff08;非四舍五入&#xff09;2、字符串轉化為整數&#xff08;非整數的字…

英語口語-文章朗讀Week9 TuesDay

朗讀文章 People living in ancient times had no alternative but to do housework manually. They fire the wood when they cook,they hand wash clothes with hands; they sweep the floor with brooms. Now, modern inventions come as a great relief to people. We co…

SpringBoot @Value注解

目錄一、非配置文件注入1、注入普通字符串2、注入JAVA系統變量3、注入表達式4、注入其他Bean屬性5、注入文件資源6、注入URL資源二、通過配置文件注入1、注入普通字符串2、注入基本類型3、注入數組類型4、注入List類型5、注入Map類型一、非配置文件注入 1、注入普通字符串 直…

C++primer 第 3 章 字符串、向量和數組 3 . 5 數組

3.5數組 數組是一種類似于標準庫類型vector&#xff08;參見3.3節&#xff0c;第86頁&#xff09;的數據結構&#xff0c;但是在性能和靈活性的權衡上又與vector有所不同。與vector相似的地方是&#xff0c;數組也是存放類型相同的對象的容器&#xff0c;這些對象本身沒有名字…

codeforces 122A-C語言解題報告

122A題目網址 題目解析 1.輸入數字(在1000以內),若能被4,7幸運數整除或只含4,7則輸出YES,否則輸出NO 舉例: 輸入: 107 輸出: NO 2.解題關鍵: 1)使用列舉法,把所有符合的幸運數列出來(int number[]) 1—2 2–224 3–22*28 24814個 2)若n是幸運數中的一個或n%幸運數0,則為YES…

SpringBoot @Value給靜態變量注入值

文章目錄一、簡介二、Value給靜態變量注入值方案一&#xff1a;set()方法設置方案二&#xff1a;PostConstruct注解修飾的方法中進行賦值三、總結一、簡介 SpringBoot 中給普通變量注入值只需在變量上添加 Value 注解即可。 application.properties 配置文件有如下配置&#…

C++primer 第 4 章 表達式 4.1基礎 4 . 2 算術運算符 4 .3 邏輯和關系運算符 4 . 4 賦值運算符 4 .5 遞增和遞減運算符 4.6成員訪問運算符

表達式由一個或多個運算對象(operand)組成&#xff0c;對表達式求值將得到一個結果(result)字面值和變量是最簡單的表達式(expression),其結果就是字面值和變量的值。把一個運算符(operator)和一個或多個運算對象組合起來可以生成較復雜的表達式 4.1基礎 有幾個基礎概念對表達…

codeforces 266B-C語言解題報告

266B題目網址 題目解析 輸入n,t,排隊情況s,輸出第t次循環后,排隊情況 舉例: 輸入: 5 1 BGGBG 輸出: GBGGB 2.輸入的n代表排隊的人數,t代表整個循環t次之后再輸出結果 3.注意點: 使用while()大循環去控制t次的循環,使用for()內層循環去遍歷整個字符串 如果if(s[j]‘B’&…

Nginx Location配置詳解

目錄一、語法二、匹配順序三、root 與 alias 的區別四、server 和 location 中的 root一、語法 Location 是 Nginx 中一個非常核心的配置&#xff0c;關于Location&#xff0c;舉個簡單的配置例子&#xff1a; server {listen 80;server_name 10.0.7.115;location / {root /d…

英語口語-文章朗讀Week9 Wednesday

英語文章 Birds of the same species flock together&#xff0c; People tend to look for someone like themselves to be friends. But having the same interests is not the only standard when we are seeking friends. In most cases, especially for adults, people l…

C++primer 第 4 章 表達式 4.7條件運算符 4.8位運算符 4.9 sizeof運算符 4.10逗號運算符 4.11類型轉換 4 . 1 2 運算符優先級表

4.7條件運算符 條件運算符(?&#xff1a;)允許我們把簡單的if else邏輯嵌入到單個表達式當中&#xff0c;條件運算符按照如下形式使用&#xff1a;cond ? expr1 : expr2;其中cond是判斷條件的表達式&#xff0c;而expr1和expr2是兩個類型相同或可能轉換為某個公共類型的表達…

Git 之 git tag標簽使用

目錄一、簡介二、本地tag操作1、創建tag標簽&#xff08;1&#xff09;創建輕量標簽&#xff08;2&#xff09;創建附注標簽2、查看tag標簽&#xff08;1&#xff09;查看標簽列表&#xff08;2&#xff09;查看標簽提交信息&#xff08;3&#xff09;在提交歷史中查看標簽3、刪…

codeforces 110A-C語言解題報告

110A題目網址 題目解析 1.輸入一個數字,如果數字中包含的4,7的數量是4或7的倍數,則輸出YES,否則輸出NO 舉例: 輸入: 40047 輸出: NO 2.注意點: 1)由于數字很長,所以使用long long int類型,使用scanf("%lld",&n)接收輸入 2)整型轉字符串,使用sprintf(字符串,“…

C++primer 第 5 章語句 5.2語句作用域 5.3條件語句 5 . 4 迭代語句 5.5跳轉語句 5.6 try語句塊和異常處理

5 . 1 簡單語句 C語言中的大多數語句都以分號結束&#xff0c;一個表達式&#xff0c;比如ival 5 , 末尾加上分號就變成了表達式語句(expression statement)。表達式語句的作用是執行表達式并丟棄掉求值結果&#xff1a;ival 5&#xff1b; // 一條沒什么實際用處的表達式語…

英語口語-文章朗讀Week9Thursday

英語文章 Everyone has his or her own dreams. Some people wants to be millionaires so they can give many generous donations later; some people want to be scientists so they can bring many conveniences to the world; some people only want to be bus-drivers s…

操作系統 內存管理相關知識

cpu執行程序的基本過程 譯碼器 輸入為n管腳&#xff0c;輸出為2^n根管腳&#xff0c;編號為從0到2^(n-1)&#xff0c;用少的輸入端控制更多的輸出端最常用的是三八譯碼器AD(Address bus)地址總線: 選中一行數據每一行 8bit 組成8吧B cpu輸入端32根線&#xff0c;輸出端就可以控…

2000年考研英語閱讀理解文章四

文章詳細解析網址 注意點 1.注意But,however等表示觀點看法轉折的詞語 2.全篇都在提及moral decline 道德下降,最后一段寫that may have more to do with life-style所以造成現象的原因應該是life-style.(主要) 前面都是在分析,最后一段點名原因 知識點 ----單詞 envy n/v…

Chrome瀏覽器必裝插件!尤其程序猿!

Chrome 瀏覽器有一個好處&#xff0c;就是插件極其豐富&#xff0c;只有你想不到的&#xff0c;沒有你找不到的&#xff0c;這恐怕是 Chrome 瀏覽器被眾多愛好者鐘愛的原因吧。 言歸正傳&#xff0c;今天來給大家推薦 10 款我自己珍藏的 Chrome 瀏覽器插件。 1、crxMouse Ch…