Spring AOP 的組成和實(shí)現(xiàn)
1. Spring AOP 簡(jiǎn)介
AOP 是一種思想,Spring AOP 是這種思想的具體實(shí)現(xiàn)。
OOP:面向?qū)ο缶幊?/p>
AOP:面向切面編程
AOP 面向切面編程,就是對(duì)某一類(lèi)事情的集中處理。
比如,我們需要在 CSDN 上進(jìn)行編輯博客、發(fā)布博客、刪除博客等操作,這些功能都是需要進(jìn)行權(quán)限校驗(yàn)的,判斷是否登錄
開(kāi)發(fā)三階段
對(duì)于公共方法的處理:
- (初級(jí)階段)每個(gè)方法都去實(shí)現(xiàn)
- (中級(jí)階段)把同一類(lèi)功能抽取成公共方法
- (高級(jí)階段)采用 AOP 的方式,對(duì)代碼無(wú)侵入實(shí)現(xiàn)
除了統(tǒng)?的用戶(hù)登錄判斷之外,AOP 還可以實(shí)現(xiàn):
- 統(tǒng)?日志記錄
- 統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì)
- 統(tǒng)一的返回格式設(shè)置
- 統(tǒng)一的異常處理
- 事務(wù)的開(kāi)啟和提交等
| 統(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: |
也就是說(shuō)使用 AOP 可以擴(kuò)充多個(gè)對(duì)象的某個(gè)能力,所以 AOP 可以說(shuō)是 OOP(Object Oriented Programming,面向?qū)ο缶幊蹋┑?strong>補(bǔ)充和完善。
2. AOP 的組成
2.1 切面(Aspect)
切面(Aspect)由切點(diǎn)(Pointcut)和通知(Advice)組成,它既包含了橫切邏輯的定義,也包括了連接點(diǎn)的定義。
切面是包含了:通知、切點(diǎn)和切面的類(lèi),相當(dāng)于 AOP 實(shí)現(xiàn)的某個(gè)功能的集合。
2.2 連接點(diǎn)(Join Point)
應(yīng)?執(zhí)行過(guò)程中能夠插入切面的?個(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 來(lái)描述)來(lái)匹配 Join Point,給滿(mǎn)足規(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ǔ)中,切面的工作被稱(chēng)之為通知。
Spring 切面類(lèi)中,可以在方法上使用以下注解,會(huì)設(shè)置方法為通知方法,在滿(mǎn)足條件后會(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)繞通知使用戶(hù) @Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。
切點(diǎn)相當(dāng)于要增強(qiáng)的方法。
AOP 整個(gè)組成部分的概念如下圖所示,以多個(gè)頁(yè)面都要訪問(wèn)用戶(hù)登錄權(quán)限為例:

既然說(shuō) AOP 是對(duì)一類(lèi)事情的集中處理,那么我們就需要明確兩點(diǎn):
- 一類(lèi)事情:處理對(duì)象的一個(gè)范圍
- 集中處理:處理的內(nèi)容是什么
我們通過(guò)生活中的一個(gè)例子來(lái)看一下:
比如,我們乘坐高鐵需要安檢
那么,我們需要處理的內(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 類(lèi):
@RequestMapping("/user")
@RestController
public class UserController {
// 獲取用戶(hù)信息
@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)行后,成功訪問(wèn):



接下來(lái),我們?cè)?UserController 類(lèi)中定義切面和切點(diǎn):
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
// 獲取用戶(hù)信息
@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 類(lèi)中使用 @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 類(lèi):
@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/hi")
public String hi(){
log.info("hi~");
return "hi~";
}
}可以看到運(yùn)行的結(jié)果中,并沒(méi)有在控制臺(tái)打印 @Before 中的內(nèi)容:


那么為什么沒(méi)有執(zhí)行呢?

我們?cè)賮?lái)看一下其他注解,@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 類(lèi)中加入異常:
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
// 獲取用戶(hù)信息
@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è)賮?lái)看一下這段代碼:

4. 切點(diǎn)表達(dá)式說(shuō)明
AspectJ 支持三種通配符
- * :匹配任意字符,只匹配一個(gè)元素(包,類(lèi),或方法,方法參數(shù))
- .. :匹配任意字符,可以匹配多個(gè)元素 ,在表示類(lèi)時(shí),必須和 * 聯(lián)合使用。
- + :表示按照類(lèi)型匹配指定類(lèi)的所有類(lèi),必須跟在類(lèi)名后面,如 com.cad.Car+ ,表示繼承該類(lèi)的 所有子類(lèi)包括本身
切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,其中 execution() 是最常用的切點(diǎn)函數(shù),用來(lái)匹配方法,語(yǔ)法為:
execution(<修飾符><返回類(lèi)型><包.類(lèi).方法(參數(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)文章
IntelliJ IDEA 配置與插件全部遷移到其他盤(pán)徹底釋放C盤(pán)空間
這篇文章給大家介紹IntelliJ IDEA 配置與插件全部遷移到其他盤(pán)徹底釋放C盤(pán)空間,本文將詳細(xì)解釋這一問(wèn)題的根源,并提供兩種實(shí)用的遷移方案,幫你把 IDEA 的所有用戶(hù)數(shù)據(jù)安全地移到其他盤(pán),徹底釋放 C 盤(pán)空間,感興趣的朋友跟隨小編一起看看吧2026-03-03
SpringBoot解析JSON數(shù)據(jù)的三種方案
JSON(JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換格式,易于人閱讀和編寫(xiě),同時(shí)也易于機(jī)器解析和生成,本文給大家介紹了SpringBoot解析JSON數(shù)據(jù)的三種方案,需要的朋友可以參考下2024-03-03
Java對(duì)int[]數(shù)組做新增刪除去重操作代碼
這篇文章主要介紹了Java里面對(duì)int[]數(shù)組做新增刪除去重實(shí)現(xiàn),這里記錄下使用int[]數(shù)組對(duì)數(shù)組進(jìn)行新增刪除去重等操作,用來(lái)更加了解java里面的集合類(lèi)思想,需要的朋友可以參考下2023-10-10
Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解
這篇文章主要介紹了Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java 中的 CompletableFuture如何讓異步編程變得簡(jiǎn)單
CompletableFuture是Java 8引入的異步編程神器,它提供了強(qiáng)大的鏈?zhǔn)讲僮?、任?wù)組合和異常處理功能,使得異步編程變得簡(jiǎn)單優(yōu)雅,本文給大家介紹Java中的CompletableFuture如何讓異步編程變得簡(jiǎn)單,感興趣的朋友跟隨小編一起看看吧2025-12-12
Java web網(wǎng)站訪問(wèn)量的統(tǒng)計(jì)
這篇文章主要為大家詳細(xì)介紹了Java web網(wǎng)站訪問(wèn)量的統(tǒng)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Java使用TCP套接字實(shí)現(xiàn)多人聊天功能詳解
這篇文章主要介紹了Java使用TCP套接字實(shí)現(xiàn)多人聊天功能,結(jié)合實(shí)例形式詳細(xì)分析了java使用socket通信實(shí)現(xiàn)tcp協(xié)議下的聊天功能客戶(hù)端與服務(wù)器端相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-10-10
Java中的可重入鎖ReentrantLock簡(jiǎn)析
這篇文章主要介紹了Java中的可重入鎖ReentrantLock簡(jiǎn)析,可重入是指同一個(gè)線程如果首次獲得了這把鎖,那么因?yàn)樗沁@把鎖的擁有者,因此有權(quán)利再次獲取這把鎖如果是不可重入鎖,那么第二次獲得鎖時(shí),自己也會(huì)被鎖擋住,需要的朋友可以參考下2023-12-12

