Spingboot利用AOP+注解实现优雅的日志记录

简介

在日常的项目中,我们经常要对controller层(也就是API层)记录各式各样的日志。比如请求参数、响应参数、耗时等信息。这些相关的日志代码其实是重复的,且繁琐的。
那么我们到底有没有办法把它们统一起来,让我们的代码更整洁呢?答案是有的。这里我们可以利用Spring AOP来获取request、response并进行日志输出打印。
但仅仅是使用AOP的话,我们也需要每一个包每一个方法配置一个切面来满足我的需求。所以我们还需要自定义一个注解,然后让切面只切到这个注解上。然后在我们需要记录日志的方法中使用这个注解。这样就可以一劳永逸了。

改造

这里延续我之前一直做的脚手架为基础,如果有需要我可以在文末克隆代码。当然代码是独立,你们也可以类推到你们的代码种。
由于SpringBoot自身是不具有AOP依赖的。所以我们首先要在pom.xml里面引入AOP依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

然后创建一个叫annotation的包。这个包是专门用来存放我们自定义的注解类。
创建一个名为AutoLog的注解类。这里下面的参数只是暂定的,为了方便以后系统扩展而已。如果不需要的话可加可不加。

/*
 * 日志配置类注解,下面参数为了以后扩展暂定,非必须
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoLog {
    /**
     * 日志内容
     */
    String value() default "";

    /**
     * 日志类型
     *
     * @return 0:操作日志;1:登录日志;2:定时任务;
     */
    int logType() default 0;

    /**
     * 操作日志类型
     *
     * @return (1查询,2添加,3修改,4删除)
     */
    int operateType() default 0;
}

然后创建一个aspect包,这个包是用于存放我们的aop类的。在创建一个LogAspect类。这个类里面就是我们的切面配置。

@Aspect
@Component
public class LogAspect {
    private final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    private StopWatch stopWatch;

    // 创建切点
    @Pointcut("@annotation(com.turkeymz.baseboot.annotation.AutoLog)")
    public void logPointCut() {

    }
    // 开始切面逻辑
    @Before(value = "logPointCut()")
    public void before(JoinPoint joinPoint) {
        stopWatch = new StopWatch();
        stopWatch.start();
        //  获取方法相关
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取请求
        HttpServletRequest request = SpringContextUtil.getHttpServletRequest();
        //获取请求参数
        String requestBody = this.getParams(joinPoint,signature);

        logger.info("请求信息:{}, 请求类型:{}, IP:{},请求方法:{}", request.getRequestURL(), request.getMethod(),
                request.getRemoteAddr(), signature.getDeclaringTypeName());
        logger.info("请求参数:{}", requestBody);
    }
    // 结束切面逻辑
    @AfterReturning(value = "logPointCut()", returning = "apiResultBody")
    public void after(APIResultBody apiResultBody) {
        stopWatch.stop();
        logger.info("响应参数:{}",apiResultBody.toString());
        logger.info("耗时: {} ms",stopWatch.getTotalTimeMillis());

    }
    // 异常切面逻辑
    @AfterThrowing(value = "logPointCut()", throwing = "exception")
    public void doAfterThrowing(Exception exception) {
        stopWatch.stop();
        // 保存异常日志记录
        logger.info("请求异常: {}",  exception.getMessage());
        logger.info("发生异常时间: {}", LocalDateTime.now());
        logger.info("耗时: {} ms",stopWatch.getTotalTimeMillis());
    }

    private String getParams(JoinPoint joinPoint,MethodSignature signature){
        StringBuffer requestBody = new StringBuffer();
        // 获取请求参数
        String[] paramNames = ((MethodSignature) signature).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        int paramLength = null == paramNames ? 0 : paramNames.length;
        if (paramLength == 0) {
            requestBody.append("{}");
        } else {
            requestBody.append("[");
            for (int i = 0; i < paramLength - 1; i++) {
                requestBody.append(paramNames[i]).append("=").append(paramValues[i]).append(", ");
            }
            requestBody.append(paramNames[paramLength - 1]).append("=").append(paramValues[paramLength - 1]).append("]");
        }
        return requestBody.toString();
    }

}

最后在我们的controller方法上面添加我们自定义的注解(@AutoLog)就可以实现自动log配置了。

    @ApiOperation("测试普通API通信")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType="path",name="name",dataType="String",required=true,value="名字",defaultValue="tom")
    })
    @GetMapping("/hello/{name}/{action}")
    @AutoLog(value = "sys say")
    public APIResultBody sayHello(@PathVariable("name") String name,@PathVariable("action") String action) throws Exception{

        String result = "Hello " + name + ", here is base boot.Are you sure to "+ action;
        if("error".equals(name)){
            throw new SysException(HttpExceptionCode.SERVER_BUSY);
        }
        Thread.sleep(1000);
        logger.info("方法在执行的过程中: {}",action);
        return APIResultBody.success(result);
    }

结果

正常异常
在这里插入图片描述在这里插入图片描述

这里简单的列一下AOP的几个相关概念。因为上面我只想传播一种用AOP+注解的方式实现log记录。但这种方式其实有很多条路径可以选择,大家可以根据AOP的特性适当选择最适合自己项目的来改。

  • Aspect(切面):声明类似于Java中的类声明,在Aspect中会包含一些Pointcut及相应的Advice。
  • Joint point(连接点):表示在程序中明确定义的点。包括方法的调用、对类成员的访问等。
  • Pointcut(切入点):表示一个组Joint point,如方法名、参数类型、返回类型等等。
  • Advice(通知):Advice定义了在Pointcut里面定义的程序点具体要做的操作,它通过(before、around、after(return、throw)、finally来区别实在每个Joint point之前、之后还是执行 前后要调用的代码。
  • Before:在执行方法前调用Advice,比如请求接口之前的登录验证。
  • Around:在执行方法前后调用Advice,这是最常用的方法。
  • After:在执行方法后调用Advice,after、return是方法正常返回后调用,after\throw是方法抛出异常后调用。
  • Finally:方法调用后执行Advice,无论是否抛出异常还是正常返回。
  • AOP proxy:AOP proxy也是Java对象,是由AOP框架创建,用来完成上述动作,AOP对象通常可以通过JDK dynamic proxy完成,或者使用CGLIb完成。
  • Weaving:实现上述切面编程的代码织入,可以在编译时刻,也可以在运行时刻,Spring和其它大多数Java框架都是在运行时刻生成代理。

附录

课题目录:https://blog.csdn.net/turkeym4/article/details/106761043
项目地址:https://gitee.com/turkeymz/baseboot

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值