1. 程式人生 > >SpringBoot基於AOP攔截請求列印日誌

SpringBoot基於AOP攔截請求列印日誌

一:需求背景.

      1.1 現在需要在特定的方法執行前後,做一些日誌處理,儘可能不要寫重複程式碼,優雅的完成處理方法日誌列印.

      1.2 簡單實現分析:如果在一些方法裡面都打日誌,這種方式是最易使用,但是從可維護,可擴充套件,耦合度來分析確實差的.

      1.3 集中處理方式:既然系統中的異常都全域性處理,也是用AOP全域性集中攔截處理日誌,豈不是更好一下吶.定義切面功能,定義切點很切指定的方法唄,前置和後置,以及環繞,以及獲得返回值.

     AOP:面向切面程式設計,橫切面向物件中的封裝類的通用的封裝功能.一句話,就是將面向物件構成的龐大的體系結構中,特定需要的部分進行水平切分.

    無論是HttpRequest請求,還是資料庫操作請求,都有記錄日誌的操作處理,將這些公用的日誌功能很切出來,進行相應的前置和後置處理.

二:實戰

     2.1:現在攔截指定的方法,獲取請求的url,method,ip,處理該請求的方法,處理該請求的方法的引數,處理該請求返回的值.

     2.2:引入AOP依賴.(SpringBoot已經幫助我們封裝好了一個starter,開箱即用)

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

  2.3 定義一個基於Http的切面.

 @Aspect註解:定義一個切面:(需要橫切面向物件的功能)

@Aspect
@Component
public class HttpAspect {}

@Pointcut註解:定義一個切點:(需要橫切的位置)


@Pointcut("execution(* com.lixing.docker.dockerboot.controller.TestController.aop(..))") public void log(){ 

}

​

@Before註解:定義前置通知(需要在切點方法的前面需要執行的動作處理).

 注意導包的正確.

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
        logger.info("前置通知日誌輸出");
        ServletRequestAttributes attributes=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request=attributes.getRequest();
        // 獲取url
        logger.info("url={}", request.getRequestURL());
        // 獲取請求method
        logger.info("method={}", request.getMethod());
        // 獲取ip
        logger.info("ip={}", request.getRemoteAddr());
        // 獲取處理請求的類方法
        logger.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName()+"()");
        // 獲取請求方法傳入的引數
        logger.info("args={}", joinPoint.getArgs());
    }

{}表示佔位就是將後面的內容輸入的位置.

說一下AspectJ中的exection表示式:

execution(* com.sample.service.impl..*.*(..))
第一個*表示方法返回值的型別任意.
第二個中間的一連串包名錶示路徑.
包名後面的表示..表示當前包及子包.
第二個星號表示的是所有的類.
.*(**)表示的是任何方法名,括號裡面表示引數,兩個點表示的是任意引數型別.

更多詳細的使用請看這篇文章總結的比較全面:AOP(execution表示式)

獲取Http請求的相關資訊使用了Spring框架的RequestContextHolder.跟一下原始碼看一下.

@After:後置通知.就是請求處理方法執行完後執行的相關處理.IDEA的很形象.

獲得很切請求方法的返回值.

@AfterReturning(returning=", pointcut="")

returning:返回值的型別.

pointcut:切點名稱.

現在執行測試一下:非常簡單的一個路徑請求.

Spring獲取Http請求相關的引數如下:

application.properties配置一下ContextPath.

server.context-path=/dockerboot
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
 * author: 
 * date: 
 * time: 
 * description: 獲取請求的各個引數
 */
@RestController
@RequestMapping("/http")
public class HttpController {
   @RequestMapping("/attribute")
    public Map<String, Object> getAttribute(String name){
       Map<String, Object> map=new HashMap();
       ServletRequestAttributes attributes=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
       HttpServletRequest request=attributes.getRequest();
       map.put("url:", request.getRequestURL());
       map.put("method:", request.getMethod());
       map.put("ContextPath", request.getContextPath());
       map.put("LocalIP", request.getLocalAddr());
       map.put("ServletPath", request.getServletPath());
       map.put("AuthType", request.getAuthType());
       map.put("SessionId", request.getSession().getId());
       map.put("Session is new", request.getSession().isNew());
       map.put("Host", request.getHeader("Host"));
       map.put("User-Agent", request.getHeader("User-Agent"));
       map.put("Connection", request.getHeader("Connection"));
       map.put("Cache-Control", request.getHeader("Cache-Control"));
       map.put("Accept-Language", request.getHeader("Accept-Language"));
       map.put("Protocol", request.getProtocol());
       map.put("LocalPort", request.getLocalPort());
       map.put("RemotePort", request.getRemotePort());
       map.put("Query", request.getQueryString());
       map.put("ContentLength", request.getContentLength());
       map.put("Encoding", request.getCharacterEncoding());
       return map;
   }
}

瀏覽器請求如下:

這樣便簡單的實現了攔截請求日誌,實現了控制層和日誌處理的分離,降低了耦合度,非常便於維護,新增其他的日誌處理也不需要額外修改控制器的功能,實現了只增強,不修改.