利用AOP監控Java介面響應超時
阿新 • • 發佈:2019-01-09
為什麼要監控
服務化介面是提供服務的,介面正確性、穩定性是最最重要的,在保證正確的同時需要儘量提高介面響應時間。
有的團隊會有專門的工具來對系統響應時間、吞吐量做監控,但如果團隊沒有這種“待遇”就需要自己來做一些工具為自己的程式碼提供服務。
自己動手,豐衣足食
AOP + Annotation 簡陋實現,能達到目的
AOP : 使用環繞方式對介面攔截,在攔截介面前後記錄時間最後計算用時
Annotation : 自定義註解在介面上設定超時時間(timeout)和超時是否傳送郵件選項(emailIfTimeout)
通過對介面實際執行時間和配置的超時時間比較,系統可以計算出介面是否超時,此時可使用日誌(或其他能通知到開發人員的方式)記錄具體哪個介面、什麼引數以及執行時間
註解可以提供更多的選項,來為自己介面服務,比如支援註解到類上,批量為介面設定了預設超時時間、支援日誌中顯示的處理方法名稱 等等...
程式碼實施
介面Annotation定義
package opsteel.oupuzw.web.logistics.bean; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 建立自定義註解輸出介面耗時(@PrintCostTime) * create on 2018-08-08 * @author Yesh */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface PrintCostTime { /** 輸出方法名稱 **/ @AliasFor("name") String name() default ""; /** * 介面超時時間,單位毫秒.預設值100毫秒 * @return 設定的超時時間 */ int timeout() default 100; /** * 註解上的列印引數 * @return 輸出的時候打印出來 */ String[] printArgs() default {}; /** * 是否開啟日誌監控功能預設開 * @return 返回ture需要傳送郵件 */ boolean enablePrint() default true; /** * 是否允許列印在預設列表裡的引數(預設 true) * @return */ boolean enablePrintDefaultArgs() default true; /** * 當介面響應超時時,是否傳送郵件.預設傳送 * @return 返回ture需要傳送郵件 */ boolean emailIfTimeout() default true; }
package opsteel.oupuzw.web.logistics.bean; import lombok.extern.log4j.Log4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * 日誌AOP監控介面響應時長 * create on 2018-08-08 * * @author Yesh */ @Component @Aspect @Log4j public class PrintCostTimeAspect { @Around("@annotation(opsteel.oupuzw.web.logistics.bean.PrintCostTime)") public Object aroundPrintCostTime(ProceedingJoinPoint pjp) throws Throwable { MethodSignature signature = (MethodSignature) pjp.getSignature(); //方法簽名 PrintCostTime printCostTime = signature.getMethod().getAnnotation(PrintCostTime.class);//從簽名註解中獲取註解內容配置項 String methodCName = printCostTime.name();//方法中文名稱[註解中獲取 不填寫 為""] String methodName = signature.getName(); // Class clazz = pjp.getTarget().getClass(); //取攔截的Class //RequestMapping requestMapping = signature.getMethod().getAnnotation(RequestMapping.class);//從這裡可以獲取 請求地址 //方法名和引數值 //String[] ArgsNames = signature.getParameterNames(); //Object[] argsValue = pjp.getArgs(); //引數列印目前沒必要 result 中可以獲取 返回的json值 // String argsLog = getArgsLog(printCostTime, ArgsNames, argsValue); long start = System.currentTimeMillis(); //開始時間 Object[] args = pjp.getArgs(); //取攔截的Args Object result = pjp.proceed(args); //執行被攔截的方法 if (printCostTime.enablePrint()==false){ //若開關為 true 開啟列印 return result; } long end = System.currentTimeMillis(); long diff = end - start;//計算耗時 //若超過配置的閥值則列印耗時日誌 long thresholdInMs = printCostTime.timeout(); if (diff >= thresholdInMs) { log.info(methodCName + ":[" + methodName + "]" + ",響應時間: " + diff + " ms"); //發郵件發作即可 } return result; } }
@PrintCostTime(name = "XXX")
在Controller註解上即可描述當前介面花費時間