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;
}
}
瀏覽器請求如下:
這樣便簡單的實現了攔截請求日誌,實現了控制層和日誌處理的分離,降低了耦合度,非常便於維護,新增其他的日誌處理也不需要額外修改控制器的功能,實現了只增強,不修改.