Spring AOP 的組成和實(shí)現(xiàn)
1. Spring AOP 簡(jiǎn)介
AOP 是一種思想,Spring AOP 是這種思想的具體實(shí)現(xiàn)。
OOP:面向?qū)ο缶幊?/p>
AOP:面向切面編程
AOP 面向切面編程,就是對(duì)某一類事情的集中處理。
比如,我們需要在 CSDN 上進(jìn)行編輯博客、發(fā)布博客、刪除博客等操作,這些功能都是需要進(jìn)行權(quán)限校驗(yàn)的,判斷是否登錄
開發(fā)三階段
對(duì)于公共方法的處理:
- (初級(jí)階段)每個(gè)方法都去實(shí)現(xiàn)
- (中級(jí)階段)把同一類功能抽取成公共方法
- (高級(jí)階段)采用 AOP 的方式,對(duì)代碼無(wú)侵入實(shí)現(xiàn)
除了統(tǒng)?的用戶登錄判斷之外,AOP 還可以實(shí)現(xiàn):
- 統(tǒng)?日志記錄
- 統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì)
- 統(tǒng)一的返回格式設(shè)置
- 統(tǒng)一的異常處理
- 事務(wù)的開啟和提交等
統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì) | 項(xiàng)目監(jiān)控:監(jiān)控項(xiàng)目請(qǐng)求流量、監(jiān)控接口的響應(yīng)時(shí)間甚至每個(gè)方法的響應(yīng)時(shí)間 |
統(tǒng)一的返回格式設(shè)置 | httpstatus: HTTP狀態(tài)碼 code: 業(yè)務(wù)狀態(tài)碼(后端響應(yīng)成功不代表業(yè)務(wù)辦理成功) msg: 業(yè)務(wù)處理失敗返回的信息 data: |
也就是說使用 AOP 可以擴(kuò)充多個(gè)對(duì)象的某個(gè)能力,所以 AOP 可以說是 OOP(Object Oriented Programming,面向?qū)ο缶幊蹋┑?strong>補(bǔ)充和完善。
2. AOP 的組成
2.1 切面(Aspect)
切面(Aspect)由切點(diǎn)(Pointcut)和通知(Advice)組成,它既包含了橫切邏輯的定義,也包括了連接點(diǎn)的定義。
切面是包含了:通知、切點(diǎn)和切面的類,相當(dāng)于 AOP 實(shí)現(xiàn)的某個(gè)功能的集合。
2.2 連接點(diǎn)(Join Point)
應(yīng)?執(zhí)行過程中能夠插入切面的?個(gè)點(diǎn),這個(gè)點(diǎn)可以是方法調(diào)用時(shí)、拋出異常時(shí),甚至修改字段 時(shí)。切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中,并添加新的行為。
連接點(diǎn)相當(dāng)于需要被增強(qiáng)的某個(gè) AOP 功能的所有方法。
2.3 切點(diǎn)(Pointcut)
Pointcut 是匹配 Join Point 的謂詞。 Pointcut 的作用就是提供?組規(guī)則(使用 AspectJ pointcut expression language 來描述)來匹配 Join Point,給滿足規(guī)則的 Join Point 添加 Advice。
切點(diǎn)相當(dāng)于保存了眾多連接點(diǎn)的一個(gè)集合(如果把切點(diǎn)看成一個(gè)表,而連接點(diǎn)就是表中?條?條 的數(shù)據(jù))。
2.4 通知(Advice)
切面也是有目標(biāo)的 ——它必須完成的工作。在 AOP 術(shù)語(yǔ)中,切面的工作被稱之為通知。
Spring 切面類中,可以在方法上使用以下注解,會(huì)設(shè)置方法為通知方法,在滿足條件后會(huì)通知本 方法進(jìn)行調(diào)用:
- 前置通知使用 @Before:通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行。
- 后置通知使用 @After:通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用。
- 返回之后通知使用 @AfterReturning:通知方法會(huì)在目標(biāo)方法返回后調(diào)用。
- 拋異常后通知使用 @AfterThrowing:通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用。
- 環(huán)繞通知使用戶 @Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。
切點(diǎn)相當(dāng)于要增強(qiáng)的方法。
AOP 整個(gè)組成部分的概念如下圖所示,以多個(gè)頁(yè)面都要訪問用戶登錄權(quán)限為例:
既然說 AOP 是對(duì)一類事情的集中處理,那么我們就需要明確兩點(diǎn):
- 一類事情:處理對(duì)象的一個(gè)范圍
- 集中處理:處理的內(nèi)容是什么
我們通過生活中的一個(gè)例子來看一下:
比如,我們乘坐高鐵需要安檢
那么,我們需要處理的內(nèi)容就是安檢;處理的范圍就是需要乘坐高鐵的人。
此處乘坐高鐵需要安檢這件事情就是切面,處理的內(nèi)容安檢就是通知,處理的范圍乘坐高鐵的人就是切點(diǎn),具體有哪些人就是連接點(diǎn)。
切點(diǎn)是一個(gè)規(guī)則,事情的處理,最終作用在方法上。
3. Spring AOP的實(shí)現(xiàn)
3.1 新建項(xiàng)目
3.2 添加 AOP 框架支持
在 pom.xml 中添加如下配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
3.3 定義切面、切點(diǎn)和通知
我們先定義 UserController 類:
@RequestMapping("/user") @RestController public class UserController { // 獲取用戶信息 @RequestMapping("/getInfo") public String getInfo(){ return "get info..."; } // 注冊(cè) @RequestMapping("/reg") public String reg(){ return "reg..."; } // Login @RequestMapping("/login") public String login(){ return "login..."; } }
運(yùn)行后,成功訪問:
接下來,我們?cè)?UserController 類中定義切面和切點(diǎn):
@Slf4j @RequestMapping("/user") @RestController public class UserController { // 獲取用戶信息 @RequestMapping("/getInfo") public String getInfo(){ log.info("get info..."); return "get info..."; } // 注冊(cè) @RequestMapping("/reg") public String reg(){ log.info("reg..."); return "reg..."; } // Login @RequestMapping("/login") public String login(){ log.info("login..."); return "login..."; } }
在 LoginAspect 類中使用 @Before 注解(通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行):
@Slf4j @Component @Aspect public class LoginAspect { @Pointcut("execution(* com.example.demo.controller.UserController.* (..))") public void pointcut(){} @Before("pointcut()") public void doBefore(){ log.info("do berore..."); } }
我們接著新建一個(gè) TestController 類:
@Slf4j @RequestMapping("/test") @RestController public class TestController { @RequestMapping("/hi") public String hi(){ log.info("hi~"); return "hi~"; } }
可以看到運(yùn)行的結(jié)果中,并沒有在控制臺(tái)打印 @Before 中的內(nèi)容:
那么為什么沒有執(zhí)行呢?
我們?cè)賮砜匆幌缕渌⒔猓?strong>@After(通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用):
@Slf4j @Component @Aspect public class LoginAspect { @Pointcut("execution(* com.example.demo.controller.UserController.* (..))") public void pointcut(){} @Before("pointcut()") public void doBefore(){ log.info("do berore..."); } @After("pointcut()") public void doAfter(){ log.info("do after..."); } }
運(yùn)行結(jié)果如下:
@AfterReturning(通知方法會(huì)在目標(biāo)方法返回后調(diào)用):
@Slf4j @Component @Aspect public class LoginAspect { @Pointcut("execution(* com.example.demo.controller.UserController.* (..))") public void pointcut(){} @Before("pointcut()") public void doBefore(){ log.info("do berore..."); } @After("pointcut()") public void doAfter(){ log.info("do after..."); } @AfterReturning("pointcut()") public void doAfterReturning(){ log.info("do after returning..."); } }
運(yùn)行以上代碼后:
可以看到 :@AfterReturning 在 @After 之前被調(diào)用。
@AfterThrowing(通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用):
我們首先在 UserController 類中加入異常:
@Slf4j @RequestMapping("/user") @RestController public class UserController { // 獲取用戶信息 @RequestMapping("/getInfo") public String getInfo(){ log.info("get info..."); return "get info..."; } // 注冊(cè) @RequestMapping("/reg") public String reg(){ log.info("reg..."); int a = 10/0; return "reg..."; } // Login @RequestMapping("/login") public String login(){ log.info("login..."); return "login..."; } }
添加 @AfterThrowing 注解:
@Slf4j @Component @Aspect public class LoginAspect { @Pointcut("execution(* com.example.demo.controller.UserController.* (..))") public void pointcut(){} @Before("pointcut()") public void doBefore(){ log.info("do berore..."); } @After("pointcut()") public void doAfter(){ log.info("do after..."); } @AfterReturning("pointcut()") public void doAfterReturning(){ log.info("do after returning..."); } @AfterThrowing("pointcut()") public void doAfterThrowing(){ log.info("do after throwing..."); } }
運(yùn)行后可以看到:
當(dāng)正常返回時(shí),執(zhí)行 @AfterReturning 注解,當(dāng)出現(xiàn)異常時(shí),不會(huì)執(zhí)行 @AfterReturning 注解;
當(dāng)出現(xiàn)異常時(shí),才會(huì)執(zhí)行 @AfterThrowing 注解,當(dāng)正常返回時(shí),不會(huì)執(zhí)行 @AfterThrowing 注解。
@Around(通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為):
添加 @Around 注解:
@Slf4j @Component @Aspect public class LoginAspect { @Pointcut("execution(* com.example.demo.controller.UserController.* (..))") public void pointcut(){} @Before("pointcut()") public void doBefore(){ log.info("do berore..."); } @After("pointcut()") public void doAfter(){ log.info("do after..."); } @AfterReturning("pointcut()") public void doAfterReturning(){ log.info("do after returning..."); } @AfterThrowing("pointcut()") public void doAfterThrowing(){ log.info("do after throwing..."); } @Around("pointcut()") public void doAround(ProceedingJoinPoint joinPoint){ log.info("環(huán)繞通知執(zhí)行之前..."); try { joinPoint.proceed(); // 調(diào)用目標(biāo)方法 } catch (Throwable e) { throw new RuntimeException(e); } log.info("環(huán)繞通知執(zhí)行之后..."); } }
運(yùn)行后界面顯示如下:
可以看到此時(shí)界面中不再有返回值,因此修改代碼如下:
@Around("pointcut()") public Object doAround(ProceedingJoinPoint joinPoint){ Object oj = null; log.info("環(huán)繞通知執(zhí)行之前..."); try { oj = joinPoint.proceed(); // 調(diào)用目標(biāo)方法 } catch (Throwable e) { throw new RuntimeException(e); } log.info("環(huán)繞通知執(zhí)行之后..."); return oj; }
此時(shí)可以看到成功返回并打印了值:
我們?cè)賮砜匆幌逻@段代碼:
4. 切點(diǎn)表達(dá)式說明
AspectJ 支持三種通配符
- * :匹配任意字符,只匹配一個(gè)元素(包,類,或方法,方法參數(shù))
- .. :匹配任意字符,可以匹配多個(gè)元素 ,在表示類時(shí),必須和 * 聯(lián)合使用。
- + :表示按照類型匹配指定類的所有類,必須跟在類名后面,如 com.cad.Car+ ,表示繼承該類的 所有子類包括本身
切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,其中 execution() 是最常用的切點(diǎn)函數(shù),用來匹配方法,語(yǔ)法為:
execution(<修飾符><返回類型><包.類.方法(參數(shù))><異常>)
5. 練習(xí):使用 AOP 統(tǒng)計(jì) UserController 每個(gè)方法的執(zhí)行時(shí)間
@Slf4j @Component @Aspect public class LoginAspect { @Pointcut("execution(* com.example.demo.controller.UserController.* (..))") public void pointcut(){} @Before("pointcut()") public void doBefore(){ log.info("do berore..."); } @After("pointcut()") public void doAfter(){ log.info("do after..."); } @AfterReturning("pointcut()") public void doAfterReturning(){ log.info("do after returning..."); } @AfterThrowing("pointcut()") public void doAfterThrowing(){ log.info("do after throwing..."); } @Around("pointcut()") public Object doAround(ProceedingJoinPoint joinPoint){ Object oj = null; log.info("環(huán)繞通知執(zhí)行之前..."); try { oj = joinPoint.proceed(); // 調(diào)用目標(biāo)方法 } catch (Throwable e) { throw new RuntimeException(e); } log.info("環(huán)繞通知執(zhí)行之后..."); return oj; } /** * * @param joinPoint 使用 AOP 統(tǒng)計(jì) UserController 每個(gè)方法的執(zhí)行時(shí)間 * @return */ @Around("pointcut()") public Object doAroundCount(ProceedingJoinPoint joinPoint){ Object oj = null; long start = System.currentTimeMillis(); try { oj = joinPoint.proceed(); // 調(diào)用目標(biāo)方法 } catch (Throwable e) { throw new RuntimeException(e); } log.info(joinPoint.getSignature().toString()+"耗時(shí):"+(System.currentTimeMillis()-start)); return oj; } }
可以看到不同的方法直接在 url 中進(jìn)行更改重新運(yùn)行界面即可獲得:
到此這篇關(guān)于Spring AOP 的組成和實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Spring AOP實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+SseEmitter和Vue3+EventSource實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送
本文主要介紹了SpringBoot+SseEmitter和Vue3+EventSource實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03解決idea出現(xiàn)的java.lang.OutOfMemoryError:?Java?heap?space的問題
我們?cè)谑褂胕dea的時(shí)候經(jīng)常會(huì)遇到一些問題,本文介紹了如何解決idea出現(xiàn)的java.lang.OutOfMemoryError:?Java?heap?space的問題,文中有相關(guān)的圖文示例,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06java實(shí)現(xiàn)簡(jiǎn)單控制臺(tái)通訊錄
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單控制臺(tái)通訊錄,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02Mybatis?Plus插入數(shù)據(jù)后獲取新數(shù)據(jù)id值的踩坑記錄
在某些情況下,需要在執(zhí)行新增后,需要獲取到新增行的id,這篇文章主要給大家介紹了關(guān)于Mybatis?Plus插入數(shù)據(jù)后獲取新數(shù)據(jù)id值的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08使用SpringBoot+nmap4j獲取端口信息的代碼詳解
這篇文章主要介紹了使用 SpringBoot + nmap4j 獲取端口信息,包括需求背景、nmap4j 的相關(guān)介紹、代碼說明(含測(cè)試代碼、改造后的代碼及參數(shù)說明),還提到了文件讀取方式和依賴引入方式,最終請(qǐng)求能獲取到數(shù)據(jù),需要的朋友可以參考下2025-01-01springboot驗(yàn)證碼的生成與驗(yàn)證的兩種方法
本文主要介紹了springboot驗(yàn)證碼的生成與驗(yàn)證的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06SpringCloud Bus消息總線的實(shí)現(xiàn)
消息總線是一種通信工具,可以在機(jī)器之間互相傳輸消息、文件等,這篇文章主要介紹了SpringCloud Bus消息總線的實(shí)現(xiàn),Spring cloud bus 通過輕量消息代理連接各個(gè)分布的節(jié)點(diǎn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05