前言
來(lái)啦老鐵乾翔!
筆者學(xué)習(xí)Spring Boot有一段時(shí)間了,附上Spring Boot系列學(xué)習(xí)文章箍镜,歡迎取閱鱼鸠、賜教:
- 5分鐘入手Spring Boot;
- Spring Boot數(shù)據(jù)庫(kù)交互之Spring Data JPA;
- Spring Boot數(shù)據(jù)庫(kù)交互之Mybatis;
- Spring Boot視圖技術(shù);
- Spring Boot之整合Swagger;
- Spring Boot之junit單元測(cè)試踩坑;
- 如何在Spring Boot中使用TestNG;
- Spring Boot之整合logback日志;
- Spring Boot之整合Spring Batch:批處理與任務(wù)調(diào)度;
- Spring Boot之整合Spring Security: 訪問(wèn)認(rèn)證;
- Spring Boot之整合Spring Security: 授權(quán)管理;
- Spring Boot之多數(shù)據(jù)庫(kù)源:極簡(jiǎn)方案;
- Spring Boot之使用MongoDB數(shù)據(jù)庫(kù)源;
- Spring Boot之多線程、異步:@Async;
- Spring Boot之前后端分離(一):Vue前端;
- Spring Boot之前后端分離(二):后端稳衬、前后端集成
- Spring Boot之前后端分離(三):登錄、登出坐漏、頁(yè)面認(rèn)證
之前在剛學(xué)習(xí)Spring Boot的時(shí)候有看到AOP薄疚,還是挺容易的碧信,但沒有實(shí)踐一下,而近期由于某些原因街夭,幾次被問(wèn)及AOP砰碴,作為系統(tǒng)學(xué)習(xí)Spring Boot的咱們,當(dāng)然不能落下Spring AOP板丽!
AOP(Aspect-Oriented Programming呈枉,面向切面編程)是一種編程范式,是面向?qū)ο缶幊痰难a(bǔ)充埃碱,它提供了另外一種思路來(lái)實(shí)現(xiàn)應(yīng)用系統(tǒng)的公共服務(wù)猖辫。AOP采用“橫切”技術(shù),解剖已封裝的對(duì)象砚殿,將這種公共服務(wù)封裝到一個(gè)可重用的模塊中啃憎,這模塊稱之為“Aspect”,即“切面”似炎⌒疗迹“切面”可降低系統(tǒng)代碼冗余,降低模塊間的耦合度羡藐,提升系統(tǒng)的可維護(hù)性贩毕。
AOP常見的使用場(chǎng)景:
1. 日志功能;
采用AOP之后仆嗦,不需要在每一處功能中添加日志收集代碼耳幢,而是在切面中統(tǒng)一完成這一步驟,提升了編程速度和代碼整潔度欧啤!
2. 業(yè)務(wù)方法調(diào)用的權(quán)限管理睛藻;
采用AOP在處理權(quán)限管理,我們不用在所有業(yè)務(wù)代碼處判斷用戶是否有權(quán)限調(diào)用此方法邢隧,而是在切面中統(tǒng)一完成這一步驟店印,減少了這種非核心業(yè)務(wù)的代碼!
3. 數(shù)據(jù)庫(kù)事務(wù)的管理倒慧;
采用AOP可以統(tǒng)一在執(zhí)行數(shù)據(jù)庫(kù)前先開啟事務(wù)按摘,在執(zhí)行完成后提交事務(wù),若執(zhí)行出錯(cuò)纫谅,則回滾事務(wù)等炫贤。
4. 緩存方面;
我們可采用AOP技術(shù)付秕,統(tǒng)一對(duì)數(shù)據(jù)進(jìn)行緩存兰珍,在下次調(diào)用時(shí),如果參數(shù)询吴、條件等未變掠河,則直接獲取數(shù)據(jù)亮元,而不再調(diào)取應(yīng)用方法。
5. 等唠摹。
AOP有點(diǎn)攔截的感覺爆捞!
AOP有一些術(shù)語(yǔ):
- Aspect;
- Joint point;
- Pointcut;
- Advice;
- AOP proxy;
- Weaving;
這些術(shù)語(yǔ)對(duì)我們理解勾拉、實(shí)踐AOP沒有太大阻礙煮甥,請(qǐng)自行腦補(bǔ)哈,我們直接上代碼開始Demo!
項(xiàng)目代碼已上傳Git Hub倉(cāng)庫(kù)藕赞,歡迎取閱:
整體步驟
- 創(chuàng)建AOP演示項(xiàng)目成肘;
- 引入AOP依賴;
- 創(chuàng)建演示用API找默;
- 編寫AOP切面類艇劫;
- 驗(yàn)證AOP代碼織入效果;
1. 創(chuàng)建AOP演示項(xiàng)目惩激;
Spring Boot項(xiàng)目創(chuàng)建可參考文章:5分鐘入手Spring Boot店煞,此處不再介紹。
2. 引入AOP依賴风钻;
在項(xiàng)目pom.xml中添加spring-boot-starter-aop依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
記得安裝一下依賴:
mvn install -Dmaven.test.skip=true -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
3. 創(chuàng)建演示用API顷蟀;
項(xiàng)目?jī)?nèi)創(chuàng)建controller包,包內(nèi)創(chuàng)建一個(gè)controller骡技,如HelloWorldController.java鸣个,HelloWorldController內(nèi)創(chuàng)建一個(gè)用于演示用的API:
package com.github.dylanz666.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author : dylanz
* @since : 10/22/2020
*/
@RestController
public class HelloWorldController {
@GetMapping("/api/hello")
public String sayHello(@RequestParam String user) {
return "Hello " + user;
}
}
此處特地寫了一個(gè)需要參數(shù)的API,我將把API處理過(guò)程進(jìn)行橫切布朦,在API請(qǐng)求前后做一些系統(tǒng)級(jí)別的操作囤萤,但不影響業(yè)務(wù)過(guò)程。
4. 編寫AOP切面類是趴;
在項(xiàng)目?jī)?nèi)創(chuàng)建config包涛舍,在包內(nèi)創(chuàng)建一個(gè)config類,如AOPConfig.java唆途,在AOPConfig編寫如下代碼:
package com.github.dylanz666.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @author : dylanz
* @since : 10/22/2020
*/
@Configuration
@Aspect
public class AOPConfig {
@Around("@within(org.springframework.web.bind.annotation.RestController)")
public Object simpleAop(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
System.out.println("client ip:" + request.getRemoteAddr());
Object[] args = proceedingJoinPoint.getArgs();
System.out.println("args:" + Arrays.asList(args));
Object object = proceedingJoinPoint.proceed();
System.out.println("return: " + object);
return object;
}
}
稍微解讀一下:
1). @Aspect富雅,聲明了這個(gè)類是個(gè)切面類;
2). @Around肛搬,聲明了一個(gè)表達(dá)式没佑,描述了要織入的目標(biāo)特性;
比如本例@within表示目標(biāo)類型帶有注解温赔,且其注解類型為 org.springframework.web.bind.annotation.RestController(如果API的注解用的是@Conrtoller蛤奢,則此處為 org.springframework.stereotype.Controller),這樣系統(tǒng)內(nèi)所有RestController方法(Rest API,也即帶有@RestController注解的controller類中的方法)被調(diào)用的時(shí)候远剩,都會(huì)執(zhí)行@Around注解的方法扣溺,也就是本例的simpleAop方法骇窍;
除了@Around(方法執(zhí)行前后織入代碼)瓜晤,還有@Before、@After腹纳、@AfterReturning痢掠、@AfterThrowing,他們均分別表示該織入代碼用于執(zhí)行方法前嘲恍、執(zhí)行方法后足画、方法返回后、方法拋出異常后佃牛,如:
@Before("@within(org.springframework.web.bind.annotation.RestController)")
public void before(JoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
System.out.println("args:" + Arrays.asList(args));
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
System.out.println("client ip:" + request.getRemoteAddr());
}
我們?cè)趫?zhí)行方法前打印請(qǐng)求參數(shù)和客戶端ip淹辞;
3). 除了Around注解的方法可以傳ProceedingJionPoint類型的參數(shù)外,其余的幾個(gè)都不能傳ProceedingJionPoint類型的參數(shù)俘侠;
4). simpleAop(名字任意)是用來(lái)織入的代碼象缀,我們可以利用參數(shù)ProceedingJoinPoint提供的方法,來(lái)對(duì)請(qǐng)求前后進(jìn)行系統(tǒng)級(jí)別操作爷速。
例如本例在接收到API請(qǐng)求還未執(zhí)行業(yè)務(wù)代碼時(shí)將客戶端ip央星、請(qǐng)求參數(shù)打印出來(lái),然后在業(yè)務(wù)代碼執(zhí)行完成后未返回給客戶端前惫东,將返回結(jié)果先打印出來(lái)莉给;
5). 通常當(dāng)切面代碼執(zhí)行完后,我們需要繼續(xù)執(zhí)行應(yīng)用代碼廉沮,并將返回對(duì)象正常返回颓遏,Object object = proceedingJoinPoint.proceed();就是為了完成這一過(guò)程;
6). 除了@within這種切面目標(biāo)匹配表達(dá)式外滞时,Spring AOP還提供了多種可選的表達(dá)式及表達(dá)式組合:
(1). within();
(2). @within;
(3). execution()叁幢,如:
- execution(public * *(...));
- execution(* set*(...));
- execution(public set*(...));
- execution(public com.xyz.service..set(...));
(4). target();
(5). @target;
(6). args();
(7). @args();
(8). @annotation();
(9). this();
(10). @Transactional;
等,讀者可自行展開學(xué)習(xí)漂洋!
5. 驗(yàn)證AOP代碼織入效果遥皂;
1). 項(xiàng)目整體結(jié)構(gòu):
2). 啟動(dòng)項(xiàng)目:
3). 訪問(wèn)API:
(手機(jī)在局域網(wǎng)內(nèi)訪問(wèn)我們的應(yīng)用路徑:http://192.168.0.101:8080/api/hello?user=dylanz)
4). 后端執(zhí)行切面代碼:
我們可以看到,在API執(zhí)行前刽漂,打印了手機(jī)的ip地址:192.168.0.100演训,同時(shí)打印了請(qǐng)求參數(shù)值:dylanz,在API對(duì)應(yīng)的方法執(zhí)行后贝咙,打印方法返回的Hello dylanz字符串給客戶端样悟,然后將該字符串傳給客戶端,之后我們便能在手機(jī)客戶端看到Hello dylanz字符串!
不難看出,我們可以將這些打印換成日志打印窟她,就能全局收集詳細(xì)的信息陈症!
或者也可在切面中做一些緩存操作、數(shù)據(jù)庫(kù)事務(wù)方面的行為等震糖。
至此录肯,我們完成了一個(gè)簡(jiǎn)單的Spring AOP案例,整個(gè)過(guò)程簡(jiǎn)單而不失靈活吊说,靈活而不失優(yōu)雅论咏,有沒有?
如果本文對(duì)您有幫助颁井,麻煩點(diǎn)贊+關(guān)注厅贪!
謝謝!