问题介绍
最近在做微信企业号的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
@Component
public 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"/>