spring boot 使用Aop通知打印控制器請求報文和返回報文問題
一、簡介
開發(fā)過程中我們往往需要寫許多例如:
@GetMapping("/id/get")
public Result getById( String id) throws Exception{
log.info("請求參數(shù)為:"+id);
verify(new VerifyParam("部門id", id));
Result result = new Result("通過id獲取部門信息成功!", service.queryById(id));
log.info("返回報文為:"+result.toString());
return result;
}
打印請求參數(shù)以及返回參數(shù)的方法,而這些操作存在于每個方法之中,使得我們代碼較為冗余,為此我們可以通過動態(tài)代理將打印參數(shù)和打印返回報文作為切面,使用切入點表達式將其切入至每個方法之中。
二、步驟
1、引入Aop相關(guān)的依賴:
<!--AOP相關(guān)的依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
引入依賴后spring-aop會加載其需要的依賴,spring默認使用aspectJ實現(xiàn)通知
其中aspectjweaver.jar中包含了解析aspectJ切入點表達式的文件,使用切入點表達式處理事務(wù)的時候也需要加入此依賴。
2、配置:
1)、創(chuàng)建配置類:
/**
* @功能描述:用于controller層操作的AOP類
* @author Administrator
*/
@Component // 將對象交由spring進行管理
@Aspect // 代表此類為一個切面類
public class ControllerAop {
}
其中@Aspect 注解代表其為一個切面管理類,可以在其下定義切入點表達式,aspectJ框架會進行解析。
2)、定義切入點表達式:
@Pointcut("execution(public * com.hzt.manage.*.web.controller..*.*(..))") // 切入點表達式
public void privilege() {
}
其中,@Pointcut代表此方法為一個切入點表達式。其value值為切入點表達式,其中value可以省略其大致格式為:
@注解(表達標簽+表達式格式)
的格式,Spring AOP支持的AspectJ切入點指示符如下:
1、 execution:用于匹配方法執(zhí)行的連接點;
2、within:用于匹配指定類型內(nèi)的方法執(zhí)行;
3、this:用于匹配當前AOP代理對象類型的執(zhí)行方法;注意是AOP代理對象的類型匹配,這樣就可能包括引入接口也類型匹配;
4、target:用于匹配當前目標對象類型的執(zhí)行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配;
5、args:用于匹配當前執(zhí)行的方法傳入的參數(shù)為指定類型的執(zhí)行方法;
6、@within:用于匹配所以持有指定注解類型內(nèi)的方法;
7、@target:用于匹配當前目標對象類型的執(zhí)行方法,其中目標對象持有指定的注解;
8、@args:用于匹配當前執(zhí)行的方法傳入的參數(shù)持有指定注解的執(zhí)行;
9、@annotation:用于匹配當前執(zhí)行方法持有指定注解的方法;
10、bean:Spring AOP擴展的,AspectJ沒有對于指示符,用于匹配特定名稱的Bean對象的執(zhí)行方法;
11、reference pointcut:表示引用其他命名切入點,只有@ApectJ風格支持,Schema風格不支持。
args中定義了切入點表達式方法執(zhí)行時候的參數(shù):
@Pointcut(value="execution(public * com.hzt.manage.*.web.controller..*.*(..))&&args(param)",argNames="param") // 切入點表達式
public void privilege1(String param) {
}
我們重點介紹 execution 方法連接點的表達式,其大概結(jié)構(gòu)為:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
1、修飾符匹配(modifier-pattern?)(可省略)
2、返回值匹配(ret-type-pattern)可以為*表示任何返回值 ,如 (String) 代表只篩選返回String類型的切入點 ,全路徑的類名等(不可省略)
3、類路徑匹配(declaring-type-pattern?)如*.manage代表一級包為任意,二級包為manage的名稱。*..manage代表所有manage包下的子類包。com..*.comtroller代表com包下所有的controller包等,*代表所有包都匹配。(不可省略)
4、方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, get* 代表以get開頭的所有方法,也可指定前綴*get代表任意后綴為get的方法(不可省略)
5、參數(shù)匹配((param-pattern))可以指定具體的參數(shù)類型,多個參數(shù)間用“,”隔開,各個參數(shù)也可以用“*”來表示匹配任意類型的參數(shù),如(String)表示匹配一個String參數(shù)的方法;(*,String) 表示匹配有兩個參數(shù)的方法,第一個參數(shù)可以是任意類型,而第二個參數(shù)是String類型;可以用(..)表示任意參數(shù)(不可省略)
6、異常類型匹配(throws-pattern?)
3、定義切面方法
@Around("privilege()")
public Object around(ProceedingJoinPoint pjd) throws Throwable {
// 獲取方法名
String className = pjd.getSignature().getClass().getName();
// 獲取執(zhí)行的方法名稱
String methodName = pjd.getSignature().getName();
/** 初始化日志打印 */
Logger log = LoggerFactory.getLogger(className);
// 定義返回參數(shù)
Object result = null;
// 記錄開始時間
long start = System.currentTimeMillis();
// 獲取方法參數(shù)
Object[] args = pjd.getArgs();
String params = "前端請求參數(shù)為:";
//獲取請求參數(shù)集合并進行遍歷拼接
for (Object object : args) {
params += object.toString() + ",";
}
params = params.substring(0, params.length() - 1);
//打印請求參數(shù)參數(shù)
log.info(className+"類的"+methodName + "的" + params);
// 執(zhí)行目標方法
result = pjd.proceed();
// 打印返回報文
log.info("方法返回報文為:" + (result instanceof Result ? (Result) result : result));
// 獲取執(zhí)行完的時間
log.info(methodName + "方法執(zhí)行時長為:" + (System.currentTimeMillis() - start));
return result;
}
5、@Around 環(huán)繞通知,如上代碼所示便是環(huán)繞通知,其有ProceedingJoinPoint參數(shù)
其中 pjd.proceed();方法代表去執(zhí)行目標方法,并獲得一個Object類型的返回值 ,我們可以對返回值進行加工處理,如裝飾加工等。
return的值為方法執(zhí)行的結(jié)果。上述代碼中首先獲取類名、方法名、方法請求參數(shù)等,進行打印的拼接,并且記錄方法執(zhí)行的開始時間,并進行打印至日志。
然后執(zhí)行方法,獲取到方法返回結(jié)果,進行打印執(zhí)行時間和執(zhí)行結(jié)果。
最后返回執(zhí)行結(jié)果。即使用Aop打印請求報文和返回報文的aop切面編碼結(jié)束。
其中@Around代表其為一個環(huán)繞通知方法,其有以下幾種類型:
1、@Before前置通知,擁有請求參數(shù) JoinPoint ,用來連接當前連接點的連接細節(jié),一般包括方法名和參數(shù)值。在方法執(zhí)行前進行執(zhí)行方法體,不能改變方法參數(shù),也不能改變方法執(zhí)行結(jié)果。
@Before(value = "privilege()")
public void before(JoinPoint joinPoint) {
}
2、@After 后置通知:在目標方法執(zhí)行之后,無論是否發(fā)生異常,都進行執(zhí)行的通知。在后置通知中,不能訪問目標方法的執(zhí)行結(jié)果(因為有可能發(fā)生異常),不能改變方法執(zhí)行結(jié)果。
@Before(value = "privilege()")
public void after(JoinPoint joinPoint) {
}
3、@AfterReturning 返回通知,在目標方法執(zhí)行結(jié)束時,才執(zhí)行的通知,同后置方法相同。其能訪問方法執(zhí)行結(jié)果(因為正常執(zhí)行)和方法的連接細節(jié),但是不能改變方法執(zhí)行結(jié)果。
@AfterReturning(value = "privilege()")
public void afterReturing(JoinPoint joinPoint,Object result) {
}
result中存放的為方法的返回值。
4、@AfterThrowing 異常通知:在目標方法出現(xiàn)異常時才會進行執(zhí)行的代碼。 throwing屬性代表方法體執(zhí)行時候拋出的異常,其值一定與方法中Exception的值需要一致。
@AfterThrowing(value="privilege()",throwing="ex")
public void exce(JoinPoint joinPoint, Exception ex) {
}
三、測試
編寫一個Controller方法
@RestController
@RequestMapping("/api/v1/dept")
public class DeptController extends BaseController{
/** 日志記錄類 */
private Logger log = LoggerFactory.getLogger(getClass());
/** 自家的service */
@Autowired
private DeptService service;
/**
* @功能描述:根據(jù)id查詢部門內(nèi)容的方法
* @return Dept
*/
@GetMapping("/id/get")
public Result getById( String id) throws Exception{
verify(new VerifyParam("部門id", id));
return new Result("通過id獲取部門信息成功!", service.queryById(id));
}
}
如此我們的controller層中的方法就大大的簡潔了。
測試結(jié)果:
2018-04-10 22:59:27.468 INFO 1460 --- [nio-8088-exec-5] nProceedingJoinPoint$MethodSignatureImpl : getById的前端請求參數(shù)為:22
2018-04-10 22:59:27.470 INFO 1460 --- [nio-8088-exec-5] nProceedingJoinPoint$MethodSignatureImpl : 方法返回報文為:Result [result_code=suc, result_message=通過id獲取部門信息成功!, data=Dept [id=22, no=22, name=22, manager=22, description=22, phone=22, createTime=Thu Apr 19 23:38:37 CST 2018, editTime=null]]
2018-04-10 22:59:27.470 INFO 1460 --- [nio-8088-exec-5] nProceedingJoinPoint$MethodSignatureImpl : getById方法執(zhí)行時長為:2
如此便能很雅觀簡潔隱式的打印請求參數(shù)、返回結(jié)果和執(zhí)行時間等!
相關(guān)文章
SpringBoot使用@Scheduled實現(xiàn)定時任務(wù)的并行執(zhí)行
在SpringBoot中,如果使用@Scheduled注解來定義多個定時任務(wù),默認情況下這些任務(wù)將會被安排在一個單線程的調(diào)度器中執(zhí)行,這意味著,這些任務(wù)將會串行執(zhí)行,而不是并行執(zhí)行,本文介紹了SpringBoot使用@Scheduled實現(xiàn)定時任務(wù)的并行執(zhí)行,需要的朋友可以參考下2024-06-06
springboot使用Hutool的JschUtil及下載安裝步驟
這篇文章主要為大家介紹了springboot使用Hutool的JschUtil的方法及下載安裝詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
Echarts+SpringMvc顯示后臺實時數(shù)據(jù)
這篇文章主要為大家詳細介紹了Echarts+SpringMvc顯示后臺實時數(shù)據(jù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-12-12
一文搞懂SpringBoot如何利用@Async實現(xiàn)異步調(diào)用
異步調(diào)用幾乎是處理高并發(fā),解決性能問題常用的手段,如何開啟異步調(diào)用?SpringBoot中提供了非常簡單的方式,就是一個注解@Async。今天我們重新認識一下@Async,以及注意事項2022-09-09
淺談Java8 的foreach跳出循環(huán)break/return
這篇文章主要介紹了Java8 的foreach跳出循環(huán)break/return,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07

