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

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

使用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 {// 写日志@Autowiredprivate 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日