Spring Boot2.X使用AOP打印接口請求日誌

Spring Boot使用AOP打印接口請求日誌

Spring的兩大特性 IOC(自動注入)和AOP(面向切面)本文講的是使用AOP面向切面的特行,在用戶訪問系統時候,根據其訪問的控制層方法 打印對應的請求日誌,例如:客戶端瀏覽器型號,電腦系統型號, 方法執行時間,請求參數等等。。。。。。。

1.理解AOP

(1)AOP就是在某一個類或方法執行前後打個標記,聲明在執行到這裏之前要先執行什麼,執行完這裏之後要接着執行什麼。

這就對應着AOP的一些專業術語以及使用場景:

Aspect(切面): 聲明類似於Java中的類聲明,在Aspect中會包含一些Pointcut及相應的Advice。
Joint point(連接點): 表示在程序中明確定義的點。包括方法的調用、對類成員的訪問等。
Pointcut(切入點): 表示一個組Joint point,如方法名、參數類型、返回類型等等。
Advice(通知): Advice定義了在Pointcut裏面定義的程序點具體要做的操作,它通過(before、around、After: 在執行方法後調用Advice,after、return是方法正常返回後調用,after\throw是方法拋出異常後調用。
Before: 在執行方法前調用Advice,比如請求接口之前的登錄驗證。
Around: 在執行方法前後調用Advice,這是最常用的方法。
Finally: 方法調用後執行Advice,無論是否拋出異常還是正常返回。

(2)AOP再白話理解就是在項目原基礎功能之上,使用AOP添加新功能 ,不會更改原來代碼 例如充值 之前代碼是沒有充值成功發送短信這個功能的,現在有了這個發送短信的需求,在不改動代碼,或者所有的充值接口統一處理,那麼我們就可以在所有類似充值的控制訪問層使用Aop,充值成功後發送一條短信。。。。。。

2.本文使用

本文使用的是註解式AOP

所需依賴

        <!-- web組件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- lombok代碼簡化插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Aop組件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- 分析客戶端信息的工具類-->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.20</version>
        </dependency>

基礎使用演示

package com.leilei.web.controller.common;

import eu.bitwalker.useragentutils.UserAgent;
import java.time.LocalDateTime;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author leilei
 * @version 1.0
 * @date 2020/01/20 19:21
 * @desc: 日誌切面 打印請求日誌
 */
@Slf4j   //lombok中日誌註解
@Aspect  //表明是一個切面類
@Component //交給Spring管理
public class LogAop {

  /** 進入方法時間戳 */
  private Long startTime;
  /** 方法結束時間戳(計時) */
  private Long endTime;

  public LogAop() {}

  /** 自定義切點 */
  private final String POINTCUT = "execution(* com.leilei.web.controller..*(..))";

  /**
   * 前置通知,方法之前執行
   * @param joinPoint
   */
  @Before(POINTCUT)
  public void doBefore(JoinPoint joinPoint) {
    // 獲取當前的HttpServletRequest對象
    ServletRequestAttributes attributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    // 獲取請求頭中的User-Agent
    UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
    // 打印請求的內容
    startTime = System.currentTimeMillis();
    log.info("請求開始時間:{}", LocalDateTime.now());
    log.info("請求Url : {}", request.getRequestURL().toString());
    log.info("請求方式 : {}", request.getMethod());
    log.info("請求ip : {}", request.getRemoteAddr());
    log.info("請求內容類型 : {}", request.getContentType());
    log.info("請求參數 : {}", Arrays.toString(joinPoint.getArgs()));
    // 系統信息
    log.info("瀏覽器 : {}", userAgent.getBrowser().toString());
    log.info("瀏覽器版本 : {}", userAgent.getBrowserVersion());
    log.info("操作系統: {}", userAgent.getOperatingSystem().toString());
  }

  @After(POINTCUT)
  public void doAfter(JoinPoint joinPoint) {
//    System.out.println("doAfter");
  }

  /**
   * 返回通知 正常結束時進入此方法
   * @param ret
   */
  @AfterReturning(returning = "ret", pointcut = POINTCUT)
  public void doAfterReturning(Object ret) {
    endTime = System.currentTimeMillis();
    log.info("請求結束時間 : {}", LocalDateTime.now());
    log.info("請求耗時 : {}", (endTime - startTime));
    // 處理完請求,返回內容
    log.info("請求返回 : {}", ret);
  }

  /**
   * 異常通知: 1. 在目標方法非正常結束,發生異常或者拋出異常時執行
   *
   * @param throwable
   */
  @AfterThrowing(pointcut = POINTCUT, throwing = "throwable")
  public void doAfterThrowing(Throwable throwable) {
    // 保存異常日誌記錄
    log.error("發生異常時間 : {}", LocalDateTime.now());
    log.error("拋出異常 : {}", throwable.getMessage());
  }
}

com.leilei.web.controller爲我自己項目定義的切點路徑 那麼當用戶訪問此路徑下所有Controller 以及之中的方法的時候,就是執行我LogAop 切點類之中的各個切點方法

@Before@AfterReturning這部分的代碼也可使用一個環繞通知@Around來替換

    @Around(POINTCUT)
    public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 獲取當前的HttpServletRequest對象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //獲取請求頭中的User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //打印請求的內容
        startTime = System.currentTimeMillis();
        // 請求信息
        // 獲取請求類名和方法名稱
        Signature signature = joinPoint.getSignature();
        // 獲取真實的方法對象
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        log.info("請求開始時間:{}" , LocalDateTime.now());
        log.info("請求Url : {}" , request.getRequestURL().toString());
        log.info("請求方式 : {}" , request.getMethod());
        log.info("請求ip : {}" , request.getRemoteAddr());
        log.info("請求內容類型 : {}", request.getContentType());
        log.info("請求參數 : {}" , Arrays.toString(joinPoint.getArgs()));
        // 系統信息
        log.info("瀏覽器 : {}", userAgent.getBrowser().toString());
        log.info("瀏覽器版本 : {}", userAgent.getBrowserVersion());
        log.info("操作系統: {}", userAgent.getOperatingSystem().toString());
        // joinPoint.proceed():當我們執行完切面代碼之後,還有繼續處理業務相關的代碼。proceed()方法會繼續執行業務代碼,並且其返回值,就是業務處理完成之後的返回值。
        Object result = joinPoint.proceed();
        log.info("請求結束時間:"+ LocalDateTime.now());
        log.info("請求耗時:{}" , (System.currentTimeMillis() - startTime));
        // 處理完請求,返回內容
        log.info("請求返回 : " , result);
        return result;
    }

3.測試

我請求項目com.leilei.web.controller路徑下的一個方法,然後再IDEA中查看是否打印日誌

在這裏插入圖片描述

那麼,本文到這裏就結束了。。。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章