Administrator
发布于 2023-04-13 / 11 阅读
0
0

SpringBoot AOP切面实现统一日志

1.在pom文件中添加aop依赖

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

<!-- 用于日志切面中,以 json 格式打印出入参-->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.5</version>
</dependency>

2.自定义一个切面类

package com.ruoyi.framework.aspectj;

import com.google.gson.Gson;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @author lsy
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年04月08日 09:40:00
 */
@Aspect
@Component
public class MyLogAspect {
    private final Logger logger = LoggerFactory.getLogger(MyLogAspect.class);


    @Pointcut("execution(public * com.ruoyi.project.study.controller..*.*(..))")
    public void myLog() {
    }

    /**
     * 在切点之前织入
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("myLog()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 打印请求相关参数
        logger.info("========================================== Before ==========================================");
        // 打印请求 url
        logger.info("URL            : {}", request.getRequestURL().toString());
        // 打印 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()));
    }

    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("myLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.info("进入日志切面方法");
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 打印出参
        logger.info("Response Args  : {}", new Gson().toJson(result));
        // 执行耗时
        logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);

        logger.info("=========================================== End ===========================================");
        // 每个请求之间空一行
        logger.info("");
        return result;
    }
}

aspect相关注解:

  • @Aspect:声明该类为一个注解类;

  • @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解等;切点定义好后,就是围绕这个切点做文章了:

  • @Before: 在切点之前,织入相关代码;

  • @After: 在切点之后,织入相关代码;

  • @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;

  • @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;

  • @Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点

执行顺序

@Around注解方法的前半部分业务逻辑
->@Before注解方法的业务逻辑
->目标方法的业务逻辑
->@Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)。
->@After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
->@AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
->@AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)

3.写一个测试类

package com.ruoyi.project.study.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lsy
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年04月08日 09:43:00
 */
@Slf4j
@RestController
@RequestMapping("/api/test")
public class ApiTestController {

    @GetMapping("getTest")
    public String getTest(Integer id,String username) {
        log.info("testGet ...");
        return "success";
    }
}

4.测试

其它方法如post、delete也可以自行测试


另外,还可以使用自定义注解的方式,可以更灵活的让指定接口输出日志。

  1. 自定义一个日志注解
    package com.ruoyi.framework.aspectj.lang.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyLog {
        /**
         * 接口描述  可以自己根据项目需求自定义多个属性
         */
        public String title() default "";
    }

     

  2. 重写上面的切入点,使用注解切入

    @Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.MyLog)")
    public void myLog() {
    
    }

     

  3. 接口上添加@MyLog(title = "测试注解接口。。。随便写")即可。

 


评论