面试八股文中经常问到设计模式,深一点的会问在项目的中的具体使用场景
展示一个关于观察者模式的具体运用场景
一个比较常见的需求场景:调用接口——>执行接口正常流程——>记录接口运行日志。
如果不考虑高并发的情况,可以直接在接口上套一个切面,在接口运行的同时将日志记录下来
如果想要将日志记录和接口运行进行解耦,可以采用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();
}