天天看點

SpringBoot三部曲之Controller 請求日志切面 AOP

SpringAOP .切面,是Spring得一大特性,使用目前是使用得面還很窄,用氣對Controller層做日志管理,其實還可以做參數校驗和RSA校驗等一系列前置操作。

在所有Controller得每一個方法裡面做請求日志記錄,會讓代碼變得很臃腫和閱讀得低效。

沒有使用統一請求日志記錄得時候,我記錄Controller的日志十分痛苦:

@RestController
@RequestMapping("gua")
public class GuaController {
    private Logger logger = LoggerFactory.getLogger(GuaController.class);
    @GetMapping("str")
    public ResponseData str() {
        logger.info("Request 請求日志: 請求控制器,請求位址 ,請求方法,請求傳回值。。。。。。還有很多需要記錄的細節,為了追溯。。。");
        return ResponseDataUtil.buildSuccess("Result String");
    }

    @GetMapping("data")
    public ResponseData data() {
        logger.info("Request 請求日志: 請求控制器,請求位址 ,請求方法,請求傳回值。。。。。。還有很多需要記錄的細節,為了追溯。。。");
        return ResponseDataUtil.buildSuccess(new User());
    }


    @GetMapping("map")
    public ResponseData map() {
        logger.info("Request 請求日志: 請求控制器,請求位址 ,請求方法,請求傳回值。。。。。。還有很多需要記錄的細節,為了追溯。。。");
        HashMap<String, Object> map = new HashMap<>(1);
        map.put("Result", "Map");
        return ResponseDataUtil.buildSuccess(map);
    }
}           

現在隻有三個映射,要是有更多呢?全都進行CV程式設計嗎?日志變得雜亂不堪,如果你說不需要記錄,那麼業務出現細微差錯或者需要追溯源頭這種情況出現,就會讓自己變得束手無策。日志得記錄,是為了輔助我們進行程式執行追溯得。但是日志記錄得雜亂無章,讓程式越來亂。

我是用AOP做日志記錄,在執行映射方法之前進行資料紀記錄,在執行完成以後進行傳回資料記錄,然後再統一輸出。

下面是我得AOP代碼,非常簡單:

@Component
@Aspect
public class RequestLogAspect {
    private final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);

    /**
     * 定義切點
     */
    @Pointcut("execution(* com.dong.gua.web.controller..*(..))")
    public void requestServer() {
    }

    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint pjp) {
        //記錄請求開始執行時間:
        long beginTime = System.currentTimeMillis();
        //擷取請求資訊
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();

        //擷取代理位址、請求位址、請求類名、方法名
        String remoteAddress = IPUtils.getProxyIP(request);
        String requestURI = request.getRequestURI();
        String methodName = pjp.getSignature().getName();
        String clazzName = pjp.getTarget().getClass().getSimpleName();

        //擷取請求參數:
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        //擷取請求參數類型
        String[] parameterNames = ms.getParameterNames();
        //擷取請求參數值
        Object[] parameterValues = pjp.getArgs();
        StringBuilder sb = new StringBuilder();
        //組合請求參數,進行日志列印
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                if (parameterNames[i].equals("bindingResult")) {
                    break;
                }
                if ((parameterValues[i] instanceof HttpServletRequest) || (parameterValues[i] instanceof HttpServletResponse)) {
                    sb.
                            append("[").
                            append(parameterNames[i]).append("=").append(parameterValues[i])
                            .append("]");
                } else {
                    sb.
                            append("[").
                            append(parameterNames[i]).append("=")
                            .append(JSON.toJSONString(parameterValues[i], SerializerFeature.WriteDateUseDateFormat))
                            .append("]");
                }
            }
        }
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Throwable throwable) {
            //請求操縱失敗
            //記錄錯誤日志
            logger.error("(ง•̀_•́)ง (っ•̀ω•́)っ          切面處理請求錯誤! IP資訊(ง•̀_•́)ง->: 【{}}】 " +
                    "URI資訊(ง•̀_•́)ง->:【{}】 請求映射控制類(ง•̀_•́)ง->:【{}】 " +
                    "請求方法(ง•̀_•́)ง->:【{}】 請求參數清單(ง•̀_•́)ง->:【{}】", remoteAddress, requestURI, clazzName, methodName,
                    sb.toString());
            throw throwable;
        }
        //請求操作成功
        String resultJosnString = "";
        if (result != null) {
            if (result instanceof ResponseData) {
                resultJosnString = JSON.toJSONString(result, SerializerFeature.WriteDateUseDateFormat);
            } else {
                resultJosnString = String.valueOf(result);
            }
        }
        //記錄請求完成執行時間:
        long endTime = System.currentTimeMillis();
        long usedTime = endTime - beginTime;
        //記錄日志
        logger.info("請求操作成功! 請求耗時:【{}】 " +
                "IP資訊(◍'౪`◍)ノ゙->: 【{}}】  URI資訊(◍'౪`◍)ノ゙->:【{}】 " +
                "請求映射控制類(◍'౪`◍)ノ゙->:【{}】 請求方法(◍'౪`◍)ノ゙->:【{}】 " +
                "請求參數清單(◍'౪`◍)ノ゙->:【{}】 傳回值(◍'౪`◍)ノ゙->:【{}】", usedTime, remoteAddress, requestURI, clazzName,
                methodName, sb.toString(), resultJosnString);

        return result;
    }

           

編寫了AOP以後,Controller的代碼是這樣的:

@RestController
@RequestMapping("gua")
public class GuaController {
    private Logger logger = LoggerFactory.getLogger(GuaController.class);
    @GetMapping("str")
    public ResponseData str() {
        return ResponseDataUtil.buildSuccess("Result String");
    }

    @GetMapping("data")
    public ResponseData data() {
        return ResponseDataUtil.buildSuccess(new User());
    }


    @GetMapping("map")
    public ResponseData map() {
        HashMap<String, Object> map = new HashMap<>(1);
        map.put("Result", "Map");
        return ResponseDataUtil.buildSuccess(map);
    }
}           

幹淨、整潔、易讀。

其請求日志是這樣的:

請求操作成功! 請求耗時:【137】 IP資訊(◍'౪`◍)ノ゙->: 【Client IP: 0:0:0:0:0:0:0:1, fromSource: request.getRemoteAddr}】  URI資訊(◍'౪`◍)ノ゙->:【/gua/str】 請求映射控制類(◍'౪`◍)ノ゙->:【GuaController】 請求方法(◍'౪`◍)ノ゙->:【str】 請求參數清單(◍'౪`◍)ノ゙->:【】 傳回值(◍'౪`◍)ノ゙->:【{"code":"0000","msg":"Result String"}】           

感興趣的小夥伴,快去試試吧!