• Spring boot 自定义注解+@Aspect实现切面输出日志,附源码下载!


    内容简介

      日志信息对于一个应用而言,无疑是至关重要的。访问记录,数据追踪,排查问题等操作,都会用到日志信息。一个优秀的日志信息包括了请求地址,入参,访问IP,出参等信息,在业务中写日志输出是相当烦锁的事情。本文介绍了利用注解+APO(@Aspect实现)的方案来实现日志的输出。使用时只需要在controller类的方法上加上一个注解即可。

    实现步骤

    添加引用

      因为使用了切面,添加aop的依赖。出参以json的方式来输出,这里使用了google的gson。

            <!-- aop 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
            <!-- google json tool -->
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.6</version>
            </dependency>

    自定义注解

      添加一个自定义注解,这里以WebLog来命名。

    /**
     * 自定义注解
     * 功能:输出日志
     */
    @Retention(RetentionPolicy.RUNTIME) //何时使用该注解,此处为运行时
    @Target({ElementType.METHOD}) //注解用于何处,此处为作用于方法上
    @Documented //注解是否将包含在 JavaDoc 中
    public @interface WebLog {
    
        /**
         * 属性
         * 日志描述信息
         * 默认为空字符串
         * @return
         */
        String description() default "";
    }

    配置切面及实现日志输出  

      面向切面编程时,常用的API拦截方式有Fliter,Interceptor,ControllerAdvice以及Aspect,它们的拦截顺序为 Fliter -> Interceptor -> ControllerAdvice -> Aspect -> controller。这里我们使用Aspect来实现。

      Aspect可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法,基于Controller层。对于统一异常处理和日志记录非常方便,有效地减少了代码的重复率。主要使用到的注解如下:

      @Aspect:添加此注解的类,用来定义切面
      @Pointcut:定义一个切点。

      @Before: 在切点之前,织入相关代码;
      @After: 在切点之后,织入相关代码;
      @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
      @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
      @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;

      执行顺序:@Around -> @Before -> Controller Method -> @Around -> @After ->@AfterReturning

      创建WebLogAspect类文件,添加@Aspect注解,在此类中定义切点,以及实现在切点前后的日志输出。

      

      定义@Around,并在此方法中,执行切点  

        /**
         * 定义 @Around 环绕
         * @param proceedingJoinPoint
         * @return
         * @throws Throwable
         */
        @Around("webLog()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            logger.info("@Around");
            long startTime = System.currentTimeMillis();    //调用接口的开始时间
            // 执行切点,调用顺序:@Before -> 接口逻辑代码 -> @After -> @AfterReturning
            Object result = proceedingJoinPoint.proceed();
            // 打印出参
            logger.info("Response Args  : {}", new Gson().toJson(result));
            // 执行耗时
            logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
            return result;
        }

      切点前后输出日志信息

        /**
         * 在切点之前织入
         * @param joinPoint
         * @throws Throwable
         */
        @Before("webLog()")
        public void doBefore(JoinPoint joinPoint) throws Throwable {
            // 开始打印请求日志
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            // 获取 @WebLog 注解的描述信息
            String methodDescription = getAspectLogDescription(joinPoint);
    
            // 打印请求相关参数
            logger.info("===================================== Start =====================================");
            // 打印请求URL
            logger.info("URL            : {}", request.getRequestURL().toString());
            // 打印描述信息
            logger.info("Description    : {}", methodDescription);
            // 打印 Http method
            logger.info("HTTP Method    : {}", request.getMethod());
            // 打印调用 controller 的全路径及执行方法
            logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
            // 打印请求的IP
            logger.info("IP             : {}", request.getRemoteAddr());
            // 打印请求入参
            logger.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));
        }
    
        /**
         * 在切点之后织入
         * @throws Throwable
         */
        @After("webLog()")
        public void doAfter() throws Throwable {
            // 接口结束后换行,方便分割查看
            logger.info("====================================== End ======================================" + LINE_SEPARATOR);
        }
    
        /**
         * 获取切面注解的描述
         * @param joinPoint
         * @return
         * @throws Exception
         */
        public String getAspectLogDescription(JoinPoint joinPoint) throws Exception {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            StringBuilder description = new StringBuilder("");
            for (Method method : methods){
                if (method.getName().equals(methodName)){
                    Class[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length){
                        description.append(method.getAnnotation(WebLog.class).description());
                        break;
                    }
                }
            }
            return description.toString();
        }

    使用

    @RestController
    public class HelloController {
    
        @RequestMapping
        @WebLog(description = "欢迎信息接口")
        public String index(){
            return "Welcome to training!";
        }
    
        @GetMapping("/getUser/{id}")
        @WebLog(description = "获取用户信息接口")
        public UserInfo getUser(@PathVariable("id") Integer id) {
            //逻辑代码省略,直接返回
            UserInfo userInfo = new UserInfo();
            userInfo.setId(id);
            userInfo.setUserName("张小跑");
            userInfo.setBirthday(new Date());
            userInfo.setUserAge(22);
            userInfo.setIntroduction("应届毕业生");
            return userInfo;
        }
    }

    日志输出效果

    源码下载

     点击下载此文中的源码,文件不大,在打开的下载页面中,点击左侧的普通下载即可,如下图。

     不能下载能留言。

  • 相关阅读:
    桥接模式
    单例模式
    SpringAOP aspectJ ProceedingJoinPoint 获取当前方法
    springMVC实现文件下载
    JAVA的变量初始化类成员变量和局部变量区别
    JAVA枚举类型的应用
    linux下svn命令大全
    转:shell脚本的一些注意事项
    转: linux下不同服务器间数据传输(rcp,scp,rsync,ftp,sftp,lftp,wget,curl)
    TCP三次握手/四次挥手详解
  • 原文地址:https://www.cnblogs.com/codecat/p/13810999.html
Copyright © 2020-2023  润新知