Use AspectJ's Aspect, Pointcut, and Around to realize the logging function (the way to intercept custom annotations)

Records : 396

Scenario : Implement the custom annotation WriteLog, which acts on the method of the class, and records a log every time the method is executed. Use AspectJ's annotations @Aspect, @Pointcut, @Around, @Before, @AfterReturning, @AfterThrowing, @After to intercept the custom annotation WriteLog to complete the logging function.

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.

Versions : JDK 1.8, SpringBoot 2.6.3, aspectj-1.9.19

1. Basics

1.1 AspectJ dependent packages

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

1.2 AspectJ annotation execution order

When AspectJ annotations are in effect, the annotation execution order.

(1) When no exception is thrown, the execution sequence: @Aspect, @Pointcut, @Around, @Before, @AfterReturning, @After, @Around.

(2) When an exception is thrown, the execution sequence: @Aspect, @Pointcut, @Around, @Before, @AfterThrowing, @After, @Around.

Note : @Around appears twice because the execution timing of @Before, @AfterReturning, @AfterThrowing, and @After is between the start and end of the @Around annotation.

2. Custom annotations

2.1 Custom annotation WriteLog

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

2.2 Scope of custom annotations

The @WriteLog annotation acts on the method.

3. Intercept custom annotations

Intercept custom annotations in WriteLogAspect. Use annotations such as @Aspect and @Pointcut to intercept custom annotations to record logs.

@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. Logging

In the example, logs are recorded by printing console messages. In production, logs can be replaced by writing to database, writing to Redis cache, and writing to 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. Use custom annotations

Use custom annotations on methods of 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. Support objects

6.1 Log object LogDto

All the attributes required to record a log are defined as an entity class.

@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 Request object ReqDto

The request object is defined as an entity class.

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

6.3 Response object ResDto

The response object is defined as an entity class.

@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. Test with Postman

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

Set request headers:

token=T20230409223256

Request data:

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

return data:

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

Above, thanks.

April 9, 2023

Guess you like

Origin blog.csdn.net/zhangbeizhen18/article/details/130044268