使用AspectJ的Aspect、Pointcut、Around实现记录日志功能(拦截自定义注解的方式)

记录:396

场景:实现自定义注解WriteLog,作用在类的方法上,每次执行方法就记录一条日志。使用AspectJ的注解@Aspect、@Pointcut、@Around、@Before、@AfterReturning、@AfterThrowing、@After拦截自定义注解WriteLog,从而完成记录日志功能。

AspectJ: The AspectJ weaver applies aspects to Java classes. It can be used as a Java agent in order to apply load-time weaving (LTW) during class-loading and also contains the AspectJ runtime classes.Eclipse AspectJ™ is a seamless aspect-oriented extension to the Java™ programming language.

版本:JDK 1.8,SpringBoot 2.6.3,aspectj-1.9.19

1.基础

1.1AspectJ依赖包

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.19</version>
</dependency>

1.2AspectJ的注解执行顺序

AspectJ的注解在生效时,注解执行顺序。

(1)没有抛出异常时,执行顺序:依次@Aspect、@Pointcut、@Around、@Before、@AfterReturning、@After、@Around。

(2)抛出异常时,执行顺序:依次@Aspect、@Pointcut、@Around、@Before、@AfterThrowing、@After、@Around。

注意:@Around出现两次,是因为@Before、@AfterReturning、@AfterThrowing、@After的执行时机是在@Around注解开始执行和结束执行之间。

2.自定义注解

2.1自定义注解WriteLog

@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WriteLog {
  String businessName() default "";
  String businessNo() default "";
}

2.2自定义注解作用范围

@WriteLog注解作用在方法上。

3.拦截自定义注解

在WriteLogAspect中拦截自定义注解。使用@Aspect和@Pointcut等注解拦截自定义注解实现记录日志。

@Component
@Aspect
@Slf4j
public class WriteLogAspect {
  // 写日志
  @Autowired
  private WriteLogManager writeLogManager;
  // 线程变量
  private static final ThreadLocal<LogDto> threadLocal = new ThreadLocal<>();
  public WriteLogAspect() {
  }
  // 日志操作切入点,指定使用注解
  @Pointcut("@annotation(com.hub.example.aop.p20230407log.annotation.WriteLog)")
  public void writeLogOpt() {
  }
  // 调用注解标记的目标方法前
  @Before("writeLogOpt()")
  public void doBefore(JoinPoint joinPoint) {
      log.info("当前执行doBefore");
  }
  // 调用注解标记的目标方法
  @Around("writeLogOpt()")
  public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
    log.info("当前执行doAround");
    Signature signature = proceedingJoinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();
    // 获取方法上注解
    WriteLog writeLog = method.getAnnotation(WriteLog.class);
    String businessName = writeLog.businessName();
    String businessNo = writeLog.businessNo();
    // 获取方法上参数并转换为JSON字符串
    Object[] argsObj = proceedingJoinPoint.getArgs();
    String reqData = JSONUtil.toJsonStr(argsObj);
    // 执行目标方法前记录日志
    LogDto logDto = new LogDto();
    logDto.setLogId(UUID.randomUUID().toString().replace("-", ""));
    RequestAttributes reqAttr = RequestContextHolder.getRequestAttributes();
    if (Objects.nonNull(reqAttr)) {
        ServletRequestAttributes servletReq = (ServletRequestAttributes) reqAttr;
        HttpServletRequest httpReq = servletReq.getRequest();
        String token = httpReq.getHeader("token");
        String sessionID = httpReq.getSession().getId();
        logDto.setToken(token);
        logDto.setSessionID(sessionID);
    }
    logDto.setReqStartTime(new Date());
    logDto.setBusinessName(businessName);
    logDto.setBusinessNo(businessNo);
    logDto.setReqData(reqData);
    threadLocal.remove();
    threadLocal.set(logDto);
    writeLogManager.insertLog(logDto);
    Object obj = null;
    // 执行目标方法
    try {
        obj = proceedingJoinPoint.proceed();
    } catch (Throwable e) {
        e.printStackTrace();
    }
    // 执行目标方法后记录日志
    logDto.setReqEndTime(new Date());
    logDto.setResData(JSONUtil.toJsonStr(obj));
    writeLogManager.updateLog(logDto);
    return obj;
  }
  @AfterReturning(pointcut = "writeLogOpt()",
       returning = "rtnObj")
  public void doAfterReturning(JoinPoint joinPoint, Object rtnObj) {
    log.info("当前执行doAfterReturning");
    LogDto logDto=threadLocal.get();
    logDto.setSuccess(true);
    logDto.setMessage("执行成功");
  }
  @AfterThrowing(pointcut = "writeLogOpt()",
        throwing = "errObj")
  public void doAfterThrowing(JoinPoint joinPoint, Throwable errObj) {
    log.info("当前执行doAfterThrowing");
    LogDto logDto=threadLocal.get();
    logDto.setSuccess(false);
    logDto.setMessage("执行失败: "+errObj.getMessage());
  }
  @After("writeLogOpt()")
  public void doAfter(JoinPoint joinPoint) {
      log.info("当前执行doAfter");
  }
}

4.记录日志

示例中以打印控制台消息方式记录日志,生产中可以替换为写数据库、写Redis缓存、写Kafka等方式记录日志。

@Slf4j
@Service
public class WriteLogManager {
  // 写日志
  public void insertLog(LogDto restReqDto) {
    log.info("日志管理器,写入一条日志到数据库.");
    log.info("日志信息: " + restReqDto.toString());
  }
  // 更新日志
  public void updateLog(LogDto restReqDto) {
    log.info("日志管理器,更新一条日志到数据库.");
    log.info("日志信息: " + restReqDto.toString());
  }
}

5.使用自定义注解

在CityQueryController的方法上使用自定义注解。

@RestController()
@RequestMapping("/hub/example/city/aspect")
@Slf4j
public class CityQueryController {
  @PostMapping("/queryCity")
  @WriteLog(businessName = "查询城市服务",
       businessNo = "queryCity")
  public ResDto queryCity(@RequestBody ReqDto reqDto) {
    log.info("当前执行CityQueryController的queryCity");
    log.info("查询操作,接收数据:" + reqDto.toString());
    try {
        log.info("查询操作,正在处理业务...");
        TimeUnit.SECONDS.sleep(2L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    ResDto resDto = ResDto.getResDto(reqDto.getCityId()
            , reqDto.getCityName(),
            "这是一个美丽的城市", new Date());
    log.info("查询操作,返回数据:" + resDto.toString());
    return resDto;
  }
}

6.支撑对象

6.1日志对象LogDto

记录一条日志需要的全部属性定义为一个实体类。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LogDto {
  private String logId;
  private String token;
  private String sessionID;
  private Date reqStartTime;
  private Date reqEndTime;
  private String businessName;
  private String businessNo;
  private String reqData;
  private String resData;
  private Boolean success;
  private String message;
}

6.2请求对象ReqDto

请求对象定义为一个实体类。

@Data
public class ReqDto {
  private Long cityId;
  private String cityName;
}

6.3响应对象ResDto

响应对象定义为一个实体类。

@Data
@Builder
public class ResDto {
  private Long cityId;
  private String cityName;
  private String cityDescribe;
  @JsonFormat(
          pattern = "yyyy-MM-dd HH:mm:ss"
  )
  private Date updateTime;
  public static ResDto getResDto(Long cityId, String cityName, String cityDescribe, Date updateTime) {
      return builder().cityId(cityId).cityName(cityName).cityDescribe(cityDescribe).updateTime(updateTime).build();
  }
}

7.使用Postman测试

测试URL:http://127.0.0.1:18200/hub-200-base/hub/example/city/aspect/queryCity

设置请求头:

token=T20230409223256

请求数据:

{
  "cityId":"20230409001",
  "cityName":"杭州"
}

返回数据:

{
  "cityId": 20230409001,
  "cityName": "杭州",
  "cityDescribe": "这是一个美丽的城市",
  "updateTime": "2023-04-09 16:17:15"
}

以上,感谢。

2023年4月9日

猜你喜欢

转载自blog.csdn.net/zhangbeizhen18/article/details/130044268
今日推荐