最近闲来无事,想着给项目加点什么东西能让系统使用和问题解决更方便,感觉拦截controller和service,输出入参并统计下该controller的响应时间挺有意思的,也能更好的发现问题解决问题。下面就上代码吧。
  做java的肯定都知道aop,那就不怎么介绍它了,直接上步骤吧。

  1. 引入aop依赖
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
  1. 然后定义一个切面,并指定一个切入点,配置要进行哪些类或方法的拦截,这里我们对所有的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.*(..))";
}
  1. 使用环绕通知,对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中,再用定时每周一次同步到库中
    }
}
  1. 环绕通知还可以统计下该controller处理执行了多长时间,可以将每个controller响应时间写入redis,再用定时每周或者多久同步到数据库一次,这样就可以统计耗时较大的逻辑并可以进行相应的处理。这里因为时间原因没有集成redis,以后有时间补上。
  2. 上面有一条过滤掉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