Skip to content

SpringBoot 使用 AOP 统一处理日志

🏷️ Spring Boot


原文:spring-boot 使用 AOP 统一处理日志


POM 文件:

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

AOP 代码:

java
//申明是个切面
@Aspect
//申明是个 spring 管理的 bean
@Component
@Order(1)
public class AspectDemo {
    private Logger log = Logger.getLogger(getClass());

    private Gson gson = new Gson();

    //申明一个切点 里面是 execution 表达式
    @Pointcut("execution(public * com.example.DemoApplication.*(..))")
    private void controllerAspect(){}

    //请求 method 前打印内容
    @Before(value = "controllerAspect()")
    public void methodBefore(JoinPoint joinPoint){
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        //打印请求内容
        log.info("===============请求内容===============");
        log.info("请求地址:"+request.getRequestURL().toString());
        log.info("请求方式:"+request.getMethod());
        log.info("请求类方法:"+joinPoint.getSignature());
        log.info("请求类方法参数:"+ Arrays.toString(joinPoint.getArgs()));
        log.info("===============请求内容===============");
    }

    //在方法执行完结后打印返回内容
    @AfterReturning(returning = "o",pointcut = "controllerAspect()")
    public void methodAfterReturing(Object o ){
        log.info("--------------返回内容----------------");
        log.info("Response 内容:"+gson.toJson(o));
        log.info("--------------返回内容----------------");
    }
}

类注解:

  • @Aspect 将一个类定义为一个切面类

  • @order(i) 标记切面类的处理优先级,i 值越小,优先级别越高。PS:可以注解类,也能注解到方法上。

方法注解:

  • @Pointcut 定义一个方法为切点里面的内容为一个表达式,下面详细介绍

  • @Before 在切点前执行方法,内容为指定的切点

  • @After 在切点后,return 前执行

  • @AfterReturning 在切入点,return 后执行,如果想对某些方法的返回参数进行处理,可以在这操作

  • @Around 环绕切点,在进入切点前,跟切点后执行

  • @AfterThrowing 在切点后抛出异常进行处理

  • @order(i) 标记切点的优先级,i 越小,优先级越高

@Pointcut 注解组合使用:

上面代码中, 我们定义了一个切点, 该切点只进行处理指定路径的:

java
@Pointcut("execution(public * com.example.DemoApplication.*(..))") private void controllerAspect(){}

现在, 我们在定义一个处理其他路径下的切点:

java
@Pointcut("execution(public * com.demo.*.*(..))") private void controllerDemo(){}

以上切点, 都是分别处理不同的内容, 如果我们需要一个切点来处理他们两者, 我们可以这么配置:

java
@Pointcut(value = "controllerAspect()||controllerDemo()") private void all(){}

@Pointcut 注解内, 直接引用其它被 @Pointcut 注解过的方法名称, 这样, 该切点就可以处理两个路径下的方法

@Pointcut 注解中的 execution 表达式: public * com.demo.*.*(..)

  • 第一个 public 表示方法的修饰符,可以用 * 代替

  • 第一个 * 表示 返回值,* 代表所有

  • com.demo.* 包路径,.* 表示路径下的所有包

  • 第三个 .* 表示路径下,所有包下的所有类的方法

  • (..) 表示不限方法参数

关于 @Order(i) 注解的一些注意事项:

  • 注解类,i 值是:值越小,优先级越高

  • 注解方法,分两种情况

    • 注解的是 @Beforei 值越小,优先级越高

    • 注解的是 @After 或者 @AfterReturning 中,i 值越大,优先级越高

总结两者的概括就是:

  • 在切入点前的操作,按 order 的值由小到大执行

  • 在切入点后的操作,按 order 的值由大到小执行延伸

有看官可能会问, 如果我要打印请求从进来,到结束,所需要的时间,定义一个成员变量,用来统计时间,同时给 @Before@AfterReturning 访问,可能会有同步的问题。所以我们引用一个 ThreadLocal<Long> 指定泛型的对象,在 @Before 记录请求的时间,在 @AfterReturning 扣除记录的时间,就是消耗的时间。

代码如下:

java
//申明是个切面
@Aspect
//申明是个 spring 管理的 bean
@Component
@Order(1)
public class AspectDemo {
    private Logger log = Logger.getLogger(getClass());

    private Gson gson = new Gson();

    ThreadLocal<Long>  startTime = new ThreadLocal<Long>();

    //申明一个切点 里面是 execution 表达式
    @Pointcut("execution(public * com.example.DemoApplication.*(..))")
    private void controllerAspect() {
    }

    //请求 method 前打印内容
    @Before(value = "controllerAspect()")
    public void methodBefore(JoinPoint joinPoint) {
        startTime.set(System.currentTimeMillis());

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        //打印请求内容
        log.info("===============请求内容===============");
        log.info("请求地址:" + request.getRequestURL().toString());
        log.info("请求方式:" + request.getMethod());
        log.info("请求类方法:" + joinPoint.getSignature());
        log.info("请求类方法参数:" + Arrays.toString(joinPoint.getArgs()));
        log.info("===============请求内容===============");
    }

    //在方法执行完结后打印返回内容
    @AfterReturning(returning = "o", pointcut = "controllerAspect()")
    public void methodAfterReturing(Object o) {
        log.info("--------------返回内容----------------");
        log.info("Response 内容:" + gson.toJson(o));
        log.info("--------------返回内容----------------");

        log.info("请求处理时间为:"+(System.currentTimeMillis() - startTime.get()));
    }
}