1. 程式人生 > >Spring AOP日誌記錄介面請求引數,執行時間

Spring AOP日誌記錄介面請求引數,執行時間

本文用spring aop方式對請求攔截,獲取請求引數以及計算介面執行時間。注意:所需的環境以及依賴有:spring各包, jdk1.8,org.slf4j.Logger (請執行匯入)

前言

在前後端分離的專案中,常因為不知道是前端還是後端的問題,而苦苦尋找bug的根源。如果能在日誌中看到前端傳過來的引數,就能直觀的知道是前端引數的問題還是後臺程式的問題,以定位到問題的根源。同時計算執行時間可瞭解到介面是否符合要求

程式碼

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import
org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.PostMapping; import
java.lang.reflect.Field; import java.util.Objects; import java.util.stream.Stream; /** * aop採集日誌(介面請求引數,介面呼叫時間) * @author Huangqing * @date 2018/7/16 11:50 */ @Aspect @Component public class ControllerInterceptor { private static String[] types = {"java.lang.Integer", "java.lang.Double", "java.lang.Float"
, "java.lang.Long", "java.lang.Short", "java.lang.Byte", "java.lang.Boolean", "java.lang.Char", "java.lang.String", "int", "double", "long", "short", "byte", "boolean", "char", "float"}; private static final Logger log = LoggerFactory.getLogger(ControllerInterceptor.class); private static ThreadLocal<Long> startTime = new ThreadLocal<Long>(); /** * 定義攔截規則:攔截com.dxyl.controller..*(..))包下面的所有類中,有@PostMapping註解的方法 */ @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)") public void controllerMethodPointcut() { } @Before("controllerMethodPointcut()") public void controller(JoinPoint point) { startTime.set(System.currentTimeMillis()); MethodSignature signature = (MethodSignature) point.getSignature(); Long count = Stream.of(signature.getMethod().getDeclaredAnnotations()) .filter(annotation -> annotation.annotationType() == PostMapping.class) .count(); String requestPath = count >= 1 ? signature.getMethod().getAnnotation(PostMapping.class).value()[0] : ""; String info = String.format("\n =======> 請求路徑: %s %s", requestPath, getMethodInfo(point)); log.info(info); } private String getMethodInfo(JoinPoint point) { String className = point.getSignature().getDeclaringType().getSimpleName(); String methodName = point.getSignature().getName(); String[] parameterNames = ((MethodSignature) point.getSignature()).getParameterNames(); StringBuilder sb = null; if (Objects.nonNull(parameterNames)) { sb = new StringBuilder(); for (int i = 0; i < parameterNames.length; i++) { // 對引數解析(引數有可能為基礎資料型別,也可能為一個物件,若為物件則需要解析物件中變數名以及值) String value = ""; if (point.getArgs()[i] == null) { value = "null"; } else { // 獲取物件型別 String typeName = point.getArgs()[i].getClass().getTypeName(); boolean flag = false; for (String t : types) { //1 判斷是否是基礎型別 if (t.equals(typeName)) { value = point.getArgs()[i].toString(); flag = true; } if (flag) { break; } } if (!flag) { //2 通過反射獲取實體類屬性 value = getFieldsValue(point.getArgs()[i]); } } sb.append(parameterNames[i] + ":" + value + "; "); } } sb = sb == null ? new StringBuilder() : sb; String info = String.format("\n =======> 請求類名: %s \n =======> 請求方法: %s \n =======> 請求引數: %s", className, methodName, sb.toString()); return info; } /** * 解析實體類,獲取實體類中的屬性 */ public static String getFieldsValue(Object obj) { //通過反射獲取所有的欄位,getFileds()獲取public的修飾的欄位 //getDeclaredFields獲取private protected public修飾的欄位 Field[] fields = obj.getClass().getDeclaredFields(); String typeName = obj.getClass().getTypeName(); for (String t : types) { if (t.equals(typeName)) { return ""; } } StringBuilder sb = new StringBuilder(); sb.append("{"); for (Field f : fields) { //在反射時能訪問私有變數 f.setAccessible(true); try { for (String str : types) { //這邊會有問題,如果實體類裡面繼續包含實體類,這邊就沒法獲取。 //其實,我們可以通遞迴的方式去處理實體類包含實體類的問題。 if (f.getType().getName().equals(str)) { sb.append(f.getName() + " : " + f.get(obj) + ", "); } } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } sb.append("}"); return sb.toString(); } /** * 計算介面執行時間 */ @AfterReturning(pointcut = "controllerMethodPointcut()") public void doAfterReturing() { long costTime = System.currentTimeMillis() - startTime.get(); log.info("\n =======> 耗費時間: " + costTime + "ms"); } }

測試結果

===2018-07-19 17:42:40.748 [http-nio-8085-exec-3] INFO  com.dxyl.aop.ControllerInterceptor Line:54  - 
 =======> 請求路徑: /list  
 =======> 請求類名: TagController 
 =======> 請求方法: getList 
 =======> 請求引數: pageNum:1; pageSize:10; 
===2018-07-19 17:42:41.522 [http-nio-8085-exec-3] INFO  com.dxyl.aop.ControllerInterceptor Line:136 - 
 =======> 耗費時間: 779ms

引數中有實體物件的也進行了解析,但仍存在一些問題,在物件中存在物件則沒有解析出來,博主想到的有遞迴,等有時間去寫好了再更新給大家。

PS:博主這樣的做法被同事否定了,原因是這樣浪費了資源,降低了效能。等待博主尋找到更好的記錄引數的做法再與大家分享,感謝觀看,如果有幫助不妨點個贊

時間沒有放過任何人,你欠歲月的將以另一種形式償還。