運用Spring Aop,一個注解實現日志記錄
1. 介紹
我們都知道Spring框架的兩大特性分別是 IOC
(控制反轉)和 AOP
(面向切面),這個是每一個Spring學習視頻里面一開始都會提到的。在日常項目中,我們也會經常使用IOC控制反轉,但是卻感覺AOP很少會運用到。其實AOP大有用處,甚至可以讓你偷偷懶。
舉一個例子,假如現在要讓你記錄每一個請求的請求IP,請求的方法,請求路徑,請求的參數,返回參數,你會怎么做?你會想,那簡單啊,我直接 log.info("xxxx") 輸出日志不行嗎,簡單!可是你要想清楚,每個請求請求的方法不一定是同一個,有一些請求可能請求編輯方法,另外一些請求可能請求登錄方法,這么多方法,你每一個方法下面都重復寫了差不多6,7行重復代碼,你覺得這好嗎?
這里,如果我們使用Aop來記錄日志,那是再好不過了。我們可以看看一個方法的執行過程來理解AOP。
下面再來看使用AOP后的執行過程。
AOP是面向切面編程,面向切面思想就是讓我們把程序想象成一條一條管道連接起來的大管道,而AOP就是在管道和管道之間的過濾網,能夠在不影響管道的情況下對管道中傳輸的數據進行記錄,修改。
使用AOP我們可以很方便地進行操作日志記錄,性能日志記錄,請求日志記錄,事務操作,安全管理等。這么說可能很抽象,再詳細點說就是各種日志記錄我們可以利用AOP來進行記錄,而不用在業務邏輯代碼中插入,安全管理就是我們可以在請求進來前對請求中的數據進行解密,在請求返回的時候對數據進行加密。這么說AOP很像Java里面的攔截器,過濾器和監聽器的結合。
具體AOP的原理就不細講了,那是另外一篇文章了,有關於動態代理。
2. 實踐
說了這么多,大白話就是AOP能讓我們在不影響原代碼的基礎上,對代碼功能進行添加,修改
在實現日志記錄功能前,我們要先復習一下 Spring Aop 里面的通知順序(連接點,通知還不知道是什么的,先去B站看一下Spring初級教程)。
先把Aop的starter依賴添加進pom文件中。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.1 定義注解
那現在我們來自定義一個注解,目的是標注該注解的方法將會記錄調用該方法的請求信息
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface MyLog {
String value() default "";
}
注解不是本篇重點,有興趣的童鞋可以搜一下。
2.2 切面類
定義我們的日志記錄切面類,切面類中記錄請求的信息。
@Component
@Aspect
@Slf4j
public class LogAspect {
//切入點為自定義注解
@Pointcut("@annotation(com.example.springaopdemo.demo2.MyLog)")
public void MyLog(){}
@Before("MyLog()")
public void Before(JoinPoint jp){
//獲取HttpServletRequest對象
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = requestAttributes.getRequest();
log.info("==========請求信息==========");
log.info("請求鏈接 : {}",request.getRequestURL().toString());
log.info("Http Method : {}",request.getMethod());
log.info("Class Method : {}.{}",jp.getSignature().getDeclaringTypeName(),jp.getSignature().getName());
log.info("Ip : {}",request.getRemoteAddr());
log.info("Args : {}", Arrays.asList(jp.getArgs()));
}
@Around("MyLog()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
log.info("執行時間 : {} ms", System.currentTimeMillis() - startTime);
log.info("返回參數 : {}", result);
return result;
}
}
通過 @Around
環繞通知我們可以進行簡單的性能記錄,如果加上 Oshi
我們甚至可以記錄執行該方法前后的CPU,內存占用率。
Oshi是Java的免費基於JNA的操作系統和硬件信息庫,Github地址是:https://github.com/oshi/oshi
它的優點是不需要安裝任何其他本機庫,並且旨在提供一種跨平台的實現來檢索系統信息,例如操作系統版本,進程,內存和CPU使用率,磁盤和分區,設備,傳感器等。
2.3 編寫測試方法
編寫一個簡單的請求,請求需要一個User對象的請求體,返回一個Map結果。
@RestController
@Slf4j
public class Controller {
@PostMapping("/test")
@MyLog
public Map<String, Object> testAop(@RequestBody User user){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("errorMsg","success");
return map;
}
}
2.4 運行結果
使用IDEA自帶的Http Client來測試api
結果:
可以看到通過利用AOP,我們沒有修改Controller中的代碼,就可以實現對Controller中每個方法請求信息的日志記錄功能。
而且我們還能夠指定該切面類是在生產環境還是開發環境下生效,只需要在切面類上添加注解。
@Profile({"dev"})
然后在配置文件中定義 spring.profiles.active
的屬性即可。
3. 總結
因為學習了Spring后,雖然知道有AOP這個東西,但是卻從來沒有真正的在實際項目中運用,這幾天研究日志記錄,卻發現AOP在日志記錄中的妙用,甚至可以利用AOP在對代碼無侵入的情況下,進行參數數據的加密和解密操作。但是,雖然說AOP使用方便,但是不能夠濫用,畢竟AOP底層使用動態代理,而動態代理要做到對方法的修改就肯定要使用到反射,反射會對性能有影響。
4. 參考文章
(7 封私信 / 66 條消息) 在一個完整的項目中,會用AOP技術么,能用簡單易懂的方式說明下什么是AOP么? - 知乎 (zhihu.com)
【SpringBoot】AOP應用實例_sysu_lluozh-CSDN博客
(20條消息) Springboot Aop 自定義注解、切面_張同學的博客-CSDN博客_springboot 自定義注解切面