1. 程式人生 > >利用AOP監控Java介面響應超時

利用AOP監控Java介面響應超時

 

為什麼要監控

  服務化介面是提供服務的,介面正確性、穩定性是最最重要的,在保證正確的同時需要儘量提高介面響應時間。

  有的團隊會有專門的工具來對系統響應時間、吞吐量做監控,但如果團隊沒有這種“待遇”就需要自己來做一些工具為自己的程式碼提供服務。

自己動手,豐衣足食

  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註解上即可描述當前介面花費時間