運用Spring Aop,一個註解實現日誌記錄

Alickx 發表於 2022-01-29
Spring

運用Spring Aop,一個註解實現日誌記錄

1. 介紹

我們都知道Spring框架的兩大特性分別是 IOC (控制反轉)和 AOP (面向切面),這個是每一個Spring學習視訊裡面一開始都會提到的。在日常專案中,我們也會經常使用IOC控制反轉,但是卻感覺AOP很少會運用到。其實AOP大有用處,甚至可以讓你偷偷懶。

舉一個例子,假如現在要讓你記錄每一個請求的請求IP,請求的方法,請求路徑,請求的引數,返回引數,你會怎麼做?你會想,那簡單啊,我直接 log.info("xxxx") 輸出日誌不行嗎,簡單!可是你要想清楚,每個請求請求的方法不一定是同一個,有一些請求可能請求編輯方法,另外一些請求可能請求登入方法,這麼多方法,你每一個方法下面都重複寫了差不多6,7行重複程式碼,你覺得這好嗎?

這裡,如果我們使用Aop來記錄日誌,那是再好不過了。我們可以看看一個方法的執行過程來理解AOP。

image-20220128235109478

下面再來看使用AOP後的執行過程。

image-20220128235523265

AOP是面向切面程式設計,面向切面思想就是讓我們把程式想象成一條一條管道連線起來的大管道,而AOP就是在管道和管道之間的過濾網,能夠在不影響管道的情況下對管道中傳輸的資料進行記錄,修改。

使用AOP我們可以很方便地進行操作日誌記錄,效能日誌記錄,請求日誌記錄,事務操作,安全管理等。這麼說可能很抽象,再詳細點說就是各種日誌記錄我們可以利用AOP來進行記錄,而不用在業務邏輯程式碼中插入,安全管理就是我們可以在請求進來前對請求中的資料進行解密,在請求返回的時候對資料進行加密。這麼說AOP很像Java裡面的攔截器,過濾器和監聽器的結合。

具體AOP的原理就不細講了,那是另外一篇文章了,有關於動態代理。

2. 實踐

說了這麼多,大白話就是AOP能讓我們在不影響原始碼的基礎上,對程式碼功能進行新增,修改

在實現日誌記錄功能前,我們要先複習一下 Spring Aop 裡面的通知順序(連線點,通知還不知道是什麼的,先去B站看一下Spring初級教程)。

image-20220129001010530

先把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

image-20220129002512748

結果:

image-20220129002550745

可以看到通過利用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 自定義註解切面