最近闲来无事,想着给项目加点什么东西能让系统使用和问题解决更方便,感觉拦截controller和service,输出入参并统计下该controller的响应时间挺有意思的,也能更好的发现问题解决问题。下面就上代码吧。
做java的肯定都知道aop,那就不怎么介绍它了,直接上步骤吧。
- 引入aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 然后定义一个切面,并指定一个切入点,配置要进行哪些类或方法的拦截,这里我们对所有的controller进行拦截。
@Component
@Aspect
public class ParamOutAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ParamOutAspect.class);
//对包下所有的controller结尾的类的所有方法增强
private final String executeExpr = "execution(* com.caxs.app..*Controller.*(..))";
}
- 使用环绕通知,对controller之前和之后增强,输出入参和响应参数,下面是所有代码。
/**
* @Author: TheBigBlue
* @Description: 拦截controller,输出入参、响应内容和响应时间
* @Date: 2019/6/17
*/
@Component
@Aspect
public class ParamOutAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ParamOutAspect.class);
//对包下所有的controller结尾的类的所有方法增强
private final String executeExpr = "execution(* com.caxs.app..*Controller.*(..))";
@Value("${spring.aop.maxReduceTime}")
private long maxReduceTime;
/**
* @param joinPoint:
* @Author: TheBigBlue
* @Description: 环绕通知,拦截controller,输出请求参数、响应内容和响应时间
* @Date: 2019/6/17
* @Return:
**/
@Around(executeExpr)
public Object processLog(ProceedingJoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//获取方法名称
String methodName = method.getName();
//获取参数名称
LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
String[] params = paramNames.getParameterNames(method);
//获取参数
Object[] args = joinPoint.getArgs();
//过滤掉request和response,不能序列化
List<Object> filteredArgs = Arrays.stream(args)
.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
.collect(Collectors.toList());
JSONObject rqsJson = new JSONObject();
rqsJson.put("rqsMethod", methodName);
rqsJson.put("rqsTime", DateUtil.getCurrentFormatDateLong19());
if (ObjectIsNullUtil.isNullOrEmpty(filteredArgs)) {
rqsJson.put("rqsParams", null);
} else {
//拼接请求参数
Map<String, Object> rqsParams = IntStream.range(0, filteredArgs.size())
.boxed()
.collect(Collectors.toMap(j -> params[j], j -> filteredArgs.get(j)));
rqsJson.put("rqsParams", rqsParams);
}
LOGGER.info(methodName + "请求信息为:" + rqsJson.toJSONString());
Object resObj = null;
long startTime = System.currentTimeMillis();
try {
//执行原方法
resObj = joinPoint.proceed(args);
} catch (Throwable e) {
LOGGER.error(methodName + "方法执行异常!", e);
throw new BusinessException(methodName + "方法执行异常!");
}
long endTime = System.currentTimeMillis();
// 打印耗时的信息
this.printExecTime(methodName, startTime, endTime);
if (resObj != null) {
if (resObj instanceof JsonResponse) {
//输出响应信息
JsonResponse resJson = (JsonResponse) resObj;
LOGGER.info(methodName + "响应信息为:" + resJson.toString());
return resJson;
} else {
return resObj;
}
} else {
return JsonResponse.success();
}
}
/**
* @param methodName:
* @param startTime:
* @param endTime:
* @Author: TheBigBlue
* @Description: 打印方法执行耗时的信息,如果超过了一定的时间,才打印
* @Date: 2019/6/17
* @Return:
**/
private void printExecTime(String methodName, long startTime, long endTime) {
long diffTime = endTime - startTime;
if (diffTime > maxReduceTime) {
LOGGER.info(methodName + " 方法执行耗时:" + diffTime + " ms");
}
//TODO 可以集成redis,将每个controller的执行时间追加到redis中,再用定时每周一次同步到库中
}
}
- 环绕通知还可以统计下该controller处理执行了多长时间,可以将每个controller响应时间写入redis,再用定时每周或者多久同步到数据库一次,这样就可以统计耗时较大的逻辑并可以进行相应的处理。这里因为时间原因没有集成redis,以后有时间补上。
- 上面有一条过滤掉request和response的操作,是因为我需要对入参输出,但是request,response不能序列化,但是有些入参是需要这些参数的,所以过滤掉,防止报错。具体的错误如下。
It is illegal to call this method if the current request is not in asynchronous mode
nested exception is java.lang.IllegalStateException: It is illegal to call this method
if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)] with root cause