zoukankan      html  css  js  c++  java
  • 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;
        }
    }

    日志输出效果

    源码下载

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

     不能下载能留言。

  • 相关阅读:
    svn cleanup failed–previous operation has not finished 解决方法
    开源SNS社区系统推荐
    从网络获取图片本地保存
    MS SQL Server 数据库连接字符串
    KeepAlive
    Configure Git in debian
    sqlserver query time
    RPi Text to Speech (Speech Synthesis)
    SQL Joins with C# LINQ
    search or reseed identity columns in sqlserver 2008
  • 原文地址:https://www.cnblogs.com/codecat/p/13810999.html
Copyright © 2011-2022 走看看