使用AOP+注解记录日志

353 阅读4分钟

前言:

在java中,记录日志是非常关键的操作。日志很多时候都是生产环境定位问题解决问题的依据。但是日志这种操作又是比较重复性的,和业务也没有关系,因此,很多项目就考虑用AOP(面向切面的编程)去完成日志记录。

据我了解,还是有少部分公司还在使用SSM框架,这种AOP记录日志需要用XML去配置。但是主流还是Springboot多一些,今天我们来讨论下注解+AOP实现日志切面。

  1. 为啥要用注解配合AOP去记录日志呢?

我相信你的心中肯定会有这种问题。试想一下,你有这种需求,你需要对方法的执行时间,或者入参,出参等进行记录。但是,你不想对每个方法都这样去记录,只想对关键的方法去执行。这样,很多情况下,你需要定义多个切点,这样,你的xml配置就非常繁琐,显得并不是很优雅。

这个时候,你就要想到注解。注解的本质上就是一个标识,它可以把特殊的东西标注出来,使得我们能狗快速定位到,那注解就满足了上面的要求。

1. 什么是注解

注解就是一个标识,它可以作用于类,方法以及变量上。比如说我们常见的override, component都是。

举例:

这是我们熟悉的override注解,它有两个比较重要的属性

        + Target: 就是表明它可以使用的地方,在例子中,只可以用在方法上
        + Retention: 就是它会保留到什么时候。有三种值
            - Source:源码阶段,就是你写的java代码中生效,编译成了class以及后面都失效了
            - Class: Class阶段,知道编译成了class阶段,都是生效的,到了运行时就失效了
            - Runtime: 一直到JVM运行阶段,这个注解都是有效的

这时候,我举几个例子,大家想想是不是这个道理。

注解作用域说明
Overridesource就是一个标识而已,告诉程序员这是一个继承的方法,对于代码编译和运行没有任何影响
Beanruntime这个注解我们都知道是spring项目启动时候,扫描并将标有@bean注解的类注入到IOC工厂中,所以他的作用域必须是运行阶段,否则过早失效,也就无意义了
未找到,如有找到的朋友请告诉我,感谢class这个是java静态代理中涉及到的注解,静态代理就是生成目标类的class文件,所以他的作用域一直到class阶段
  1. 先来生命一个注解

这个注解就是用来标注,什么样的方法需要被AOP拦截来记录日志

    * 作用域:肯定是方法
    * 保留失效:肯定是runtime期间,因为AOP的底层是动态代理,就是运行时候的增强,这也很好理解

注解代码如下:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRecord {
    String operate() default "";
}

当然我这里声明了一个注解的属性,operate,我们可以标注这个方法的操作类型,这个可以根据具体业务去实现,后面我们会取出来也打印在LOG中

下面,我们就要定义一个切面,切点就是这个注解啦,然后我们可以定义它的环绕通知去记录方法执行时间等。

@Aspect//声明这是一个切面类
@Slf4j
@Component
public class LogRecordAspect  {
    //切点为加了注解的方法
    @Pointcut("@annotation(LogRecord)")
    public void logRecord() {

    }

    @Around("logRecord()")
    public void handle(ProceedingJoinPoint joinPoint) {
        log.info("Monitor the method");
        //通过切面拿到signature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //拿到方法
        Method method = signature.getMethod();
        //拿到注解
        LogRecord annotation = method.getAnnotation(LogRecord.class);
        //最终拿到注解上的操作
        String operate = annotation.operate();
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            //这里代码方法放行
            joinPoint.proceed();
            stopWatch.stop();
            log.info(stopWatch.prettyPrint());
        } catch (Throwable throwable) {
            stopWatch.stop();
            log.error(throwable.getMessage());
        }
    }


}

我在项目定义了一个简单的controller接口,然后加上了注解,我们看下结果吧。

@LogRecord
@GetMapping("testAOP")
public String testAOP() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {

    }

    return "success";
}

可以看到很灵活的实现对于方法的拦截,记录日志等。这里推荐一下StopWatch来打印方法执行时间,大家可以去了解一下,后续我们会来好好讲下这个功能。