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