Spring的事件通知机制(publish-event)的运用场景——日志处理

98 阅读2分钟

面试八股文中经常问到设计模式,深一点的会问在项目的中的具体使用场景

展示一个关于观察者模式的具体运用场景

一个比较常见的需求场景:调用接口——>执行接口正常流程——>记录接口运行日志。

如果不考虑高并发的情况,可以直接在接口上套一个切面,在接口运行的同时将日志记录下来

如果想要将日志记录和接口运行进行解耦,可以采用Spring的事件通知机制(publish-event)来实现

定义一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

   /**
    * 描述
    * @return {String}
    */
   String value() default "";

}

定义一个日志实体类

@Data
public class SysLogDTO {

   /**
    * 编号
    */
   private Long id;

   /**
    * 日志类型
    */
   @NotBlank(message = "日志类型不能为空")
   private String logType;

   /**
    * 日志标题
    */
   @NotBlank(message = "日志标题不能为空")
   private String title;

   /**
    * 创建者
    */
   private String createBy;

   /**
    * 更新时间
    */
   private LocalDateTime updateTime;

   /**
    * 操作IP地址
    */
   private String remoteAddr;

   /**
    * 用户代理
    */
   private String userAgent;

   /**
    * 请求URI
    */
   private String requestUri;

   /**
    * 操作方式
    */
   private String method;

   /**
    * 操作提交的数据
    */
   private String params;

   /**
    * 执行时间
    */
   private Long time;

   /**
    * 异常信息
    */
   private String exception;

   /**
    * 服务ID
    */
   private String serviceId;

   /**
    * 创建时间区间 [开始时间,结束时间]
    */
   private LocalDateTime[] createTime;



}

定义一个日志事件

@Getter
@AllArgsConstructor
public class SysLogEvent {

   private final SysLogDTO sysLog;

}

定义一个事件监听,监听SysLogEvent,并异步执行后续逻辑

/**
 * 异步监听日志事件
 */
@Slf4j
@AllArgsConstructor
public class SysLogListener {


    @Async
    @Order
    @EventListener(SysLogEvent.class)
    public void saveSysLog(SysLogEvent event) {
        SysLogDTO sysLog = event.getSysLog();
        System.out.println("日志异步写入:" + sysLog);//正常情况是写入数据库
    }

}

定义注解切面

主要是从上下文中获取日志内容和发布日志事件,以下是简化演示处理

@Slf4j
@Aspect
@RequiredArgsConstructor
public class SysLogAspect {

   private final ApplicationEventPublisher publisher;


   @SneakyThrows
   @Around("@annotation(sysLog)")
   public Object around(ProceedingJoinPoint point, SysLog sysLog) {
      String strClassName = point.getTarget().getClass().getName();
      String strMethodName = point.getSignature().getName();
      log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);

      String value = sysLog.value();
      SysLogDTO logDTO = SysLogUtils.getSysLog();
      logDTO.setTitle(value);
      // 发送异步日志事件
      Long startTime = System.currentTimeMillis();
      Object obj;
      try {
         obj = point.proceed();
      }
      catch (Exception e) {
         logDTO.setLogType(LogTypeEnum.ERROR.getType());
         logDTO.setException(e.getMessage());
         throw e;
      }
      finally {
         Long endTime = System.currentTimeMillis();
         logDTO.setTime(endTime - startTime);
         publisher.publishEvent(new SysLogEvent(logDTO));//发布事件
      }
      return obj;
   }

}

定义一个配置类,将监听器和切面交由spring管理

@EnableAsync
@Configuration
@AllArgsConstructor
@ConditionalOnWebApplication
public class LogAutoConfiguration {

   @Bean
   public SysLogListener sysLogListener() {
      return new SysLogListener();
   }

   @Bean
   public SysLogAspect sysLogAspect(ApplicationEventPublisher publisher) {
      return new SysLogAspect(publisher);
   }

}

测试


@RequestMapping(value = "/hi",method = RequestMethod.GET)
@SysLog("异步写入日志测试" )
public String sayHello(HttpServletRequest request){
    
    return "hello publish-event from "+request.getServerPort();
}