事件监听驱动 与 异步
事件监听驱动优点:解耦,将 事件和业务进行解耦,通过@Asyc注解可以实现异步
事件监听驱动优点:解耦,将 事件和业务进行解耦,通过@Asyc注解可以实现异步
我们监听事件之前要有事件源source,事件(Event),发布事件(publishEvent),然后才能到监听事件。
事件驱动机制是观察者模式(称发布订阅)具体实现,事件对象(Event)相当于被观察对象(Subject), 事件监听(EventListener) 相当于观察者(Observer)
事件源
实现ApplicationContextAware接口
重写setApplicationContext方法
获取ApplicationContext对象
public class FilePhysicalDeleteEventListener implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
ApplicationContextAware接口
ApplicationContext作用具体参考 https://blog.csdn.net/Pluto372/article/details/130139628
在Spring/SpringMVC中,我们拿到IOC容器无非有三种方式,那就是使用ApplicationContext接口下的三个实现类:ClassPathXmlApplicationContext
、FileSystemXmlApplicationContext
、AnnotationConfigApplicationContext
。
但是SpringBoot的强大让我们无需再配置xml文件,也因此我们无法通过上述方式拿到ApplicationContext对象,所以当在项目需要用到spring中的bean对象时,一般做法就是实现ApplicationContextAware
接口,通过这个接口就可以获取到ApplicationContext对象,进入从ApplicationContext
中获取所需要bean对象。
总结:通过ApplicationContextAware接口获取ApplicationContext对象,ApplicationContext可以获取IOC容器中的bean
发布事件
通过ApplicationContext对象发布
private void physicalDeleteFileByStorageEngine(List<RPanFile> realFileRecords) {
//映射为路径集合
List<String> realFilePathList = realFileRecords.stream().map(RPanFile::getRealPath).collect(Collectors.toList());
DeleteFileContext context = new DeleteFileContext();
context.setRealFilePathList(realFilePathList);
try {
storageEngine.delete(context);
} catch (IOException e) {
//记录错误日志
applicationContext.publishEvent(new ErrorLogEvent(this, "实体文件:" + JSON.toJSONString(realFilePathList) + ", 物理删除失败,请执行手动删除", RPanConstants.ZERO_LONG));
}
}
事件实体
事件实体需要继承ApplicationEvent对象
@Getter
@Setter
@EqualsAndHashCode
@ToString
public class ErrorLogEvent extends ApplicationEvent {
/**
* 错误日志的内容
*/
private String errorMsg;
/**
* 当前登录的用户ID
*/
private Long userId;
public ErrorLogEvent(Object source, String errorMsg, Long userId) {
super(source);
this.errorMsg = errorMsg;
this.userId = userId;
}
}
监听事件
注解@EventListener(ErrorLogEvent.class)方式监听事件
@Component
public class ErrorLogEventListener {
@Autowired
private IErrorLogService iErrorLogService;
/**
* 监听系统错误日志事件,并保存到数据库中
*
* @param event
*/
@EventListener(ErrorLogEvent.class) //监听这个事件
@Async(value = "eventListenerTaskExecutor")
public void saveErrorLog(ErrorLogEvent event){
RPanErrorLog record = new RPanErrorLog();
//保存到数据库,调用mp 方法
iErrorLogService.save(record);
}
}
实现异步
实现异步,并指定线程池任务执行器,value
为指定线程池执行器的 bean的名称。
@Async(value = “eventListenerTaskExecutor”)
当 ErrorLogEvent 事件发生时,相关的处理方法将会以异步的方式执行,
并且将使用指定的任务执行器 “eventListenerTaskExecutor”。不阻塞主线程,提高响应。
public class ErrorLogEventListener {
@Autowired
private IErrorLogService iErrorLogService;
/**
* 监听系统错误日志事件,并保存到数据库中
*
* @param event
*/
@EventListener(ErrorLogEvent.class) //监听这个事件
@Async(value = "eventListenerTaskExecutor")
public void saveErrorLog(ErrorLogEvent event){
RPanErrorLog record = new RPanErrorLog();
//保存到数据库,调用mp 方法
iErrorLogService.save(record);
}
}
注入綫程池
通过Configuration注解和Bean注解以配置类的方式注入线程池
,通过name属性指定bean的名称
@SpringBootConfiguration
public class TreadPoolConfig {
// 注入bean
// 任务线程池执行器
@Bean(name = "eventListenerTaskExecutor")
public ThreadPoolTaskExecutor eventListenerTaskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// new FutureTask<>();
taskExecutor.setCorePoolSize(10);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setQueueCapacity(2048);
taskExecutor.setThreadNamePrefix("event-listener-thread");
//拒绝策略
// CallerRunsPolicy 既不抛弃任务也不抛出异常,直接使用主线程来执行此任务
// abort 直接拒绝并抛异常
// discard 丢弃不抛出异常
// discardEldest 抛弃队列中等待最久的
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return taskExecutor;
}
}
事件驱动机制,与MQ消息队列比较
MQ驱动的作用:解耦、异步、削峰
优点:解耦,异步,削峰,消息不丢失,解决高并发消息
缺点:难维护
事件驱动机制:解耦、异步,做不到削峰。
优点:维护简单
缺点:大并发扛不住,适合单机环境,消息可能丢失,无法削峰
总结MQ的优点
- 异步解耦
- 应对高并发的消息
- 适用于分布式环境
- 消息不丢失
- 对消息进行 削峰
总结MQ的缺点:
- 分布式场景下引发的复杂问题,如分布式事务等。