Spring Boot 使用 AOP 记录日志

武培轩 2021年03月01日 160次浏览

在项目开发中经常,日志系统是必不可少的,特别是管理系统,对于重要的操作都会有操作日志,然而这个操作不需要我们在相应的方法中一个一个的去实现,这肯定是不合适的,这样的操作无疑是加大了开发量,而且不易维护,所以实际项目中总是利用AOP(Aspect Oriented Programming)即面向切面编程来记录系统中的操作日志。

下面就来介绍如何在 Spring Boot 中 使用 AOP 记录日志:

加入依赖

首先加入 AOP 依赖:

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

创建日志注解类

创建一个日志注解类,这样就可以在需要记录日志的方法上加上注解就可以记录日志了,注解内容如下:

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AopLogger {

    String describe() default "";
}

配置 AOP 切面

定义一个 AopLoggerAspect 切面类,用 @Aspect 声明该类为切面类。

@Aspect
@Component
public class AopLoggerAspect {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("@annotation(com.wupx.aop.logger.annotation.AopLogger)")
    public void aopLoggerAspect() {
    }

    /**
     * 环绕触发
     *
     * @param point
     * @return
     */
    @Around("aopLoggerAspect()")
    public Object doAround(ProceedingJoinPoint point) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        Object result = null;
        long startTime = System.currentTimeMillis();
        try {
            result = point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error(throwable.getMessage());
        }
        String describe = getAopLoggerDescribe(point);
        if (StringUtils.isBlank(describe)) {
            describe = "-";
        }
        // 打印请求相关参数
        logger.info("========================================== Start ==========================================");
        logger.info("Describe       : {}", describe);
        // 打印请求 url
        logger.info("URL            : {}", request.getRequestURL());
        logger.info("URI            : {}", request.getRequestURI());
        // 打印 Http method
        logger.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        logger.info("Class Method   : {}.{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
        // 打印请求的 IP
        logger.info("IP             : {}", request.getRemoteAddr());
        // 打印请求入参
        logger.info("Request Args   : {}", point.getArgs());
        // 打印请求出参
        logger.info("Response Args  : {}", result);
        logger.info("Time Consuming : {} ms", System.currentTimeMillis() - startTime);
        logger.info("=========================================== End ===========================================");
        return result;
    }

    /**
     * 获取注解中对方法的描述信息
     *
     * @param joinPoint 切点
     * @return describe
     */
    public static String getAopLoggerDescribe(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AopLogger controllerLog = method.getAnnotation(AopLogger.class);
        return controllerLog.describe();
    }
}

其中 @Pointcut 是定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解等。

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

测试

接下来编写 Controller 层来进行测试:

@RestController
@RequestMapping("/user")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    @AopLogger(describe = "添加用户")
    public String addUser(@RequestBody User user) {
        UserEntity userEntity = new UserEntity();
        BeanUtils.copyProperties(user, userEntity);
        return userService.addUser(userEntity);
    }
}

只需要在接口上填写 @AopLogger 就可以记录操作日志。

启动服务,通过 PostMan 请求 http://localhost:8080/user 接口,输出的日志如下所示:

可以看到把入参、出参以及接口信息都记录了下来,是不是很简单呢,只需要简单几步就可以实现 AOP 日志,大家可以自己实践下。

本文的完整代码在 https://github.com/wupeixuan/SpringBoot-Learnaop-logger 目录下。