zoukankan      html  css  js  c++  java
  • Spring中AOP使用小结

    在开发中经常需要对调用链路进行日志记录,如果只是简单的在调用方法中打印日志,显然无法达到效果。所以需要有一种更好的方式统一管理。而Spring中提供的aop就可以很好的解决此问题。

    参考学习网站:https://www.cnblogs.com/bigben0123/p/7779357.html

    需求:① 调用链路 ② 日志统一整合

    /**
     * @author betterLearning
     */
    @Aspect
    @Component
    public class LogAspect {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
    
        /** 定义被切包及其子包内的方法 */
        @Pointcut(value = "execution(* com.it.betterlearn.controller..*.*(..))")
        public void controllerPointcut(){}
    
        @Around("controllerPointcut()")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            // 接收请求
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 记录请求内容
            LOGGER.info("URL:{}",request.getRequestURL().toString());
            LOGGER.info("IP:{}",request.getRemoteAddr());
            String classMethod = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();
            LOGGER.info("CLASS_METHOD:{}", classMethod);
            // 记录请求入参
            Object[] args = pjp.getArgs();
            if (args.length > 0) {
                StringBuilder sb = new StringBuilder();
                for (Object arg : args) {
                    sb.append(JSON.toJSONString(arg));
                }
                LOGGER.info("classMethod:{}|params:{}", classMethod, sb.toString());
            }
            // 获取调用链路号(全局唯一),约定俗称key:traceId。value:全局唯一号
            String traceId = MDC.get("traceId");
            // 如果没有,自身产生一个。比如当前服务属于调用链路中的第一个。
            if (traceId == null) {
                MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));
            }
            Exception exception = null;
            Object result = null;
            long begin = System.currentTimeMillis();
            try {
                return result = pjp.proceed();
            } catch (Exception ex) {
                exception = ex;
                throw ex;
            } finally {
                long spendTime = System.currentTimeMillis()-begin;
                if (exception != null) {
                    LOGGER.info("classMethod:{}|spendTime:{}|exception:{}", classMethod, spendTime, JSON.toJSONString(exception));
                } else {
                    LOGGER.info("classMethod:{}|spendTime:{}|result:{}", classMethod, spendTime, JSON.toJSONString(result));
                }
                // 从MDC删除调用链路号
                if (traceId != null) {
                    MDC.remove(traceId);
                }
                MDC.clear();
            }
        }
    }

    最后还需要在日志格式中添加traceId的打印:

    <property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>

    基于上面的方式,代码看起来还是有点凌乱,而且打印的内容没有做封装,不好管理和迭代,甚至在高并发情况下,日志出现串行(虽然有链路号,可以判断),所以下面继续优化。

    封装正常请求信息头和错误信息头

    /**
     * @author betterLearning
     */
    @Data
    public class RequestInfo {
        private String ip;
        private String url;
        private String httpMethod;
        private String classMethod;
        private Object requestParams;
        private Object result;
        private Long timeCost;
    }
    /**
     * @author betterLearning
     */
    @Data
    public class RequestErrorInfo {
        private String ip;
        private String url;
        private String httpMethod;
        private String classMethod;
        private Object requestParams;
        private RuntimeException exception;
    }

    切面代码,注:此处没有把traceId加上。

      1 /**
      2  * @author betterLearning
      3  */
      4 @Aspect
      5 @Component
      6 public class LogAspect {
      7     private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
      8 
      9     @Pointcut("execution(* com.it.demo.controller..*(..))")
     10     public void controllerPointcut(){};
     11 
     12     /**
     13      * 正常通知环绕体
     14      * @param proceedingJoinPoint
     15      * @return
     16      * @throws Throwable
     17      */
     18     @Around("controllerPointcut()")
     19     public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
     20         long start = System.currentTimeMillis();
     21         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
     22         HttpServletRequest request = attributes.getRequest();
     23         // 封装请求
     24         RequestInfo requestInfo = new RequestInfo();
     25         requestInfo.setIp(request.getRemoteAddr());
     26         requestInfo.setUrl(request.getRequestURL().toString());
     27         requestInfo.setHttpMethod(request.getMethod());
     28         requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
     29                 proceedingJoinPoint.getSignature().getName()));
     30         requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
     31         // 执行目标方法
     32         Object result = proceedingJoinPoint.proceed();
     33         // 封装执行结果
     34         requestInfo.setResult(result);
     35         // 答应请求耗时
     36         requestInfo.setTimeCost(System.currentTimeMillis() - start);
     37         LOGGER.info("Request Info: {}", JSON.toJSONString(requestInfo));
     38         return result;
     39     }
     40 
     41     /**
     42      * 异常通知。区别于正常环绕,不打印耗时;
     43      * @param joinPoint
     44      * @param e
     45      */
     46     @AfterThrowing(pointcut = "controllerPointcut()", throwing = "e")
     47     public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
     48         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
     49         HttpServletRequest request = attributes.getRequest();
     50         RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
     51         requestErrorInfo.setIp(request.getRemoteAddr());
     52         requestErrorInfo.setUrl(request.getRequestURL().toString());
     53         requestErrorInfo.setHttpMethod(request.getMethod());
     54         requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),
     55                 joinPoint.getSignature().getName()));
     56         requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
     57         requestErrorInfo.setException(e);
     58         LOGGER.info("Error Request Info: {}", JSON.toJSONString(requestErrorInfo));
     59     }
     60     /**
     61      * 获取请求参数。注:对于@PathVariable以及@RequestParam注解传递的参数无法打印出参数名
     62      * @param proceedingJoinPoint
     63      * @return
     64      */
     65     private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
     66         //参数名
     67         String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
     68         //参数值
     69         Object[] paramValues = proceedingJoinPoint.getArgs();
     70         return buildRequestParam(paramNames, paramValues);
     71     }
     72 
     73     private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
     74         //参数名
     75         String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
     76         //参数值
     77         Object[] paramValues = joinPoint.getArgs();
     78         return buildRequestParam(paramNames, paramValues);
     79     }
     80 
     81     /**
     82      * 封装参数名和参数值,一一对应。注:此处对于文件对象做了特殊处理
     83      * @param paramNames
     84      * @param paramValues
     85      * @return
     86      */
     87     private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
     88         Map<String, Object> requestParams = new HashMap<>(16);
     89         for (int i = 0; i < paramNames.length; i++) {
     90             Object value = paramValues[i];
     91             //如果是文件对象
     92             if (value instanceof MultipartFile) {
     93                 MultipartFile file = (MultipartFile) value;
     94                 //获取文件名
     95                 value = file.getOriginalFilename();
     96             }
     97             requestParams.put(paramNames[i], value);
     98         }
     99         return requestParams;
    100     }
    101 }
  • 相关阅读:
    简单的自我介绍
    学习总结(北航助教陈彦吉)
    2019春季第十二周助教总结
    2019春季第十一周助教总结
    第十一周作业
    2019春季第十周助教总结
    2019春季第九周助教总结
    第九周作业
    2019春季第八周助教总结
    第八周作业
  • 原文地址:https://www.cnblogs.com/gzhcsu/p/14339892.html
Copyright © 2011-2022 走看看