最近在做微信企業號的Saas套件開發,因而前端頁面都是使用H5做的。為了提高開發效率,使得前后端基本能夠并行開發,我們后端開發人員和前端開發人員就約定使用前后端分離的開發方式。
一旦采用前后端分離的開發方式,我們后端人員就只提供接口了。因為我們是采用sPRing + springmvc_mybatis的通用架構。所以這種純接口的開發非常方便。
但是在開發調試過程中遇到一個痛點就是在測試環境中一旦遇到錯誤比較難定位問題,因為微信中的調試器打開比較麻煩,所以要看一個問題需要耗費比較長的時間(相信使用微信工具調試的人深知此事)。所以一般情況下,后端開發人員都在日志中打印前端傳給后端的請求參數以及返回給前端的結果。因而代碼中充斥著這樣的邏輯。
/** * xxxx * * @param queryVo * @return */ @RequestMapping(value = "/xxxx") @ResponseBody public Map<String, Object> xxxx(OrderStatisticsQueryParams queryParams) { logger.debug("請求參數:" + JsonUtil.toJSONString(queryParams)); // PROCESS result return result; }
如果只是一個兩個接口也就罷了,但是之后我們打算都采用前后端分離的方式來開發,因而代碼中必定到處都充斥著這樣的重復邏輯。
因為我想到了可以使用AOP來解決這個問題。
問題解決方案
1.使用自定義注解來標識哪些接口需要打印參數日志,而不是一刀切,所有的接口都需要打印日志2.考慮線上的情況。一般來講打印日志的需求只會在開發測試階段才會有,而正常情況下線上不需要打印請求參數。而且打印參數也會浪費線上的資源。
二話不說,先上自定義日志注解的代碼
@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD})public @interface SystemLog{ String description() default ""; // 方法描述}再上切面的代碼
package com.zk.platform.aop;import java.lang.reflect.Method;import java.util.Map;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 com.alibaba.fastjson.JSON;import com.google.common.collect.Maps;import com.zk.platform.annotation.SystemLog;import com.zk.platform.util.PropertiesUtil;@Aspect@Componentpublic class SystemLogAop{ private static final boolean ENABLE_METHOD_LOGGING = "true".equals(PropertiesUtil.getSysProp("method.args.logging", "false")); private static final Logger logger = LoggerFactory.getLogger(SystemLogAop.class); @Pointcut("@annotation(com.zk.platform.annotation.SystemLog)") public void systemLogPointCut() { } @Before("systemLogPointCut()") public void beforeExec(JoinPoint joinPoint) { // 不開啟的話則不打印日志 if (!ENABLE_METHOD_LOGGING) { return; } try { String targetName = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Object[] arguments = joinPoint.getArgs(); String[] parameterNames = ms.getParameterNames(); Method method = ms.getMethod(); SystemLog systemLog = method.getAnnotation(SystemLog.class); if (arguments != null && parameterNames != null && arguments.length == parameterNames.length && arguments.length > 0) { Object argObj = null; if (arguments.length == 1) { argObj = arguments[0]; } else { Map<String, Object> map = Maps.newHashMapWithExpectedSize(arguments.length); for (int i = 0; i < arguments.length; i++) { map.put(parameterNames[i], arguments[i]); } argObj = map; } logger.debug("{}.{}({}) args are:{}", targetName, methodName, systemLog.description(), JSON.toJSONString(argObj)); } else { logger.debug("{}.{}({}) invoked and no args", targetName, methodName, systemLog.description()); } } catch (Exception e) { logger.warn("打印日志異常:{}", e); } } @AfterReturning(pointcut = "systemLogPointCut()", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { // 不開啟的話則不打印日志 if (!ENABLE_METHOD_LOGGING) { return; } try { String targetName = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Method method = ms.getMethod(); SystemLog systemLog = method.getAnnotation(SystemLog.class); logger.debug("{}.{}({}) return value is:{}", targetName, methodName, systemLog.description(), JSON.toJSONString(result)); } catch (Exception e) { logger.warn("打印日志異常:{}", e); } }}注意,日志功能是否生效由參數"method.args.logging來控制,保證線上不受影響。
問題解決過程中遇到的問題
配置后,發現切面沒有生效。
解決方案如下
<!-- 最重要:::如果放在spring-context.xml中,這里的aop設置將不會生效 --> <aop:aspectj-autoproxy proxy-target-class="true"/>
在spring配置文件中加上這句
<aop:aspectj-autoproxy proxy-target-class="true"/>
新聞熱點
疑難解答