Spring Boot2.X使用AOP打印接口请求日志

Spring Boot使用AOP打印接口请求日志

Spring的两大特性 IOC(自动注入)和AOP(面向切面)本文讲的是使用AOP面向切面的特行,在用户访问系统时候,根据其访问的控制层方法 打印对应的请求日志,例如:客户端浏览器型号,电脑系统型号, 方法执行时间,请求参数等等。。。。。。。

1.理解AOP

(1)AOP就是在某一个类或方法执行前后打个标记,声明在执行到这里之前要先执行什么,执行完这里之后要接着执行什么。

这就对应着AOP的一些专业术语以及使用场景:

Aspect(切面): 声明类似于Java中的类声明,在Aspect中会包含一些Pointcut及相应的Advice。
Joint point(连接点): 表示在程序中明确定义的点。包括方法的调用、对类成员的访问等。
Pointcut(切入点): 表示一个组Joint point,如方法名、参数类型、返回类型等等。
Advice(通知): Advice定义了在Pointcut里面定义的程序点具体要做的操作,它通过(before、around、After: 在执行方法后调用Advice,after、return是方法正常返回后调用,after\throw是方法抛出异常后调用。
Before: 在执行方法前调用Advice,比如请求接口之前的登录验证。
Around: 在执行方法前后调用Advice,这是最常用的方法。
Finally: 方法调用后执行Advice,无论是否抛出异常还是正常返回。

(2)AOP再白话理解就是在项目原基础功能之上,使用AOP添加新功能 ,不会更改原来代码 例如充值 之前代码是没有充值成功发送短信这个功能的,现在有了这个发送短信的需求,在不改动代码,或者所有的充值接口统一处理,那么我们就可以在所有类似充值的控制访问层使用Aop,充值成功后发送一条短信。。。。。。

2.本文使用

本文使用的是注解式AOP

所需依赖

        <!-- web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- lombok代码简化插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Aop组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- 分析客户端信息的工具类-->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.20</version>
        </dependency>

基础使用演示

package com.leilei.web.controller.common;

import eu.bitwalker.useragentutils.UserAgent;
import java.time.LocalDateTime;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author leilei
 * @version 1.0
 * @date 2020/01/20 19:21
 * @desc: 日志切面 打印请求日志
 */
@Slf4j   //lombok中日志注解
@Aspect  //表明是一个切面类
@Component //交给Spring管理
public class LogAop {

  /** 进入方法时间戳 */
  private Long startTime;
  /** 方法结束时间戳(计时) */
  private Long endTime;

  public LogAop() {}

  /** 自定义切点 */
  private final String POINTCUT = "execution(* com.leilei.web.controller..*(..))";

  /**
   * 前置通知,方法之前执行
   * @param joinPoint
   */
  @Before(POINTCUT)
  public void doBefore(JoinPoint joinPoint) {
    // 获取当前的HttpServletRequest对象
    ServletRequestAttributes attributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    // 获取请求头中的User-Agent
    UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
    // 打印请求的内容
    startTime = System.currentTimeMillis();
    log.info("请求开始时间:{}", LocalDateTime.now());
    log.info("请求Url : {}", request.getRequestURL().toString());
    log.info("请求方式 : {}", request.getMethod());
    log.info("请求ip : {}", request.getRemoteAddr());
    log.info("请求内容类型 : {}", request.getContentType());
    log.info("请求参数 : {}", Arrays.toString(joinPoint.getArgs()));
    // 系统信息
    log.info("浏览器 : {}", userAgent.getBrowser().toString());
    log.info("浏览器版本 : {}", userAgent.getBrowserVersion());
    log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
  }

  @After(POINTCUT)
  public void doAfter(JoinPoint joinPoint) {
//    System.out.println("doAfter");
  }

  /**
   * 返回通知 正常结束时进入此方法
   * @param ret
   */
  @AfterReturning(returning = "ret", pointcut = POINTCUT)
  public void doAfterReturning(Object ret) {
    endTime = System.currentTimeMillis();
    log.info("请求结束时间 : {}", LocalDateTime.now());
    log.info("请求耗时 : {}", (endTime - startTime));
    // 处理完请求,返回内容
    log.info("请求返回 : {}", ret);
  }

  /**
   * 异常通知: 1. 在目标方法非正常结束,发生异常或者抛出异常时执行
   *
   * @param throwable
   */
  @AfterThrowing(pointcut = POINTCUT, throwing = "throwable")
  public void doAfterThrowing(Throwable throwable) {
    // 保存异常日志记录
    log.error("发生异常时间 : {}", LocalDateTime.now());
    log.error("抛出异常 : {}", throwable.getMessage());
  }
}

com.leilei.web.controller为我自己项目定义的切点路径 那么当用户访问此路径下所有Controller 以及之中的方法的时候,就是执行我LogAop 切点类之中的各个切点方法

@Before@AfterReturning这部分的代码也可使用一个环绕通知@Around来替换

    @Around(POINTCUT)
    public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取当前的HttpServletRequest对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取请求头中的User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //打印请求的内容
        startTime = System.currentTimeMillis();
        // 请求信息
        // 获取请求类名和方法名称
        Signature signature = joinPoint.getSignature();
        // 获取真实的方法对象
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        log.info("请求开始时间:{}" , LocalDateTime.now());
        log.info("请求Url : {}" , request.getRequestURL().toString());
        log.info("请求方式 : {}" , request.getMethod());
        log.info("请求ip : {}" , request.getRemoteAddr());
        log.info("请求内容类型 : {}", request.getContentType());
        log.info("请求参数 : {}" , Arrays.toString(joinPoint.getArgs()));
        // 系统信息
        log.info("浏览器 : {}", userAgent.getBrowser().toString());
        log.info("浏览器版本 : {}", userAgent.getBrowserVersion());
        log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
        // joinPoint.proceed():当我们执行完切面代码之后,还有继续处理业务相关的代码。proceed()方法会继续执行业务代码,并且其返回值,就是业务处理完成之后的返回值。
        Object result = joinPoint.proceed();
        log.info("请求结束时间:"+ LocalDateTime.now());
        log.info("请求耗时:{}" , (System.currentTimeMillis() - startTime));
        // 处理完请求,返回内容
        log.info("请求返回 : " , result);
        return result;
    }

3.测试

我请求项目com.leilei.web.controller路径下的一个方法,然后再IDEA中查看是否打印日志

在这里插入图片描述

那么,本文到这里就结束了。。。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章