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;
    }
}

日志输出效果

源码下载

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

 不能下载能留言。

 

posted @ 2020-10-14 10:10  代码猫  阅读(1303)  评论(0编辑  收藏  举报