Spring:事件驱动
简介
事件驱动其实它是一种抽象出来的,处理事情的一种过程,他里面包含:事件源、事件、事件消费者
就比如说:到晚上11点了(事件源),你妈妈喊你睡觉(事件),这时你去睡觉了(事件消费者)
事件驱动要解决的问题
松耦合
在复杂系统中,往往存在多个组件相互耦合的情况,如果将组件之间的耦合关系抽象成“事件(Event)”,让事件担任组件之间的通信任务,就能降低、解除组件之间的耦合关系。
事件驱动模型,实际上是将组件之间的耦合关系转移到了“事件(Event)”上,但是对于某个领域而言事件(Event)一般具有通用性并且不会频繁变更实现逻辑,所以事件驱动模型可以很好地实现组件之间的解耦。
异步处理
在一些业务场景中,顺序、阻塞式地执行任务会遇到一些比较耗时的中间步骤,但是不希望整个流程都停下来等待这些中间步骤完成,而是触发一个异步操作然后继续执行当前任务,在收到异步操作处理完成的消息之后再执行相关的处理。
使用事件驱动模型实现异步任务的一般思路是:当遇到耗时较大、没有同步执行要求的操作时,针对这个操作触发一个事件,将这个事件加入到任务队列中,直到有一个进程(线程)能够获取并执行这个任务,才开始执行这个任务。
跟踪状态变化
在存储实体模型的业务中通常需要修改实体模型的数据,对于部分业务场景需要存储、使用实体模型的历史变更记录,例如什么时间对实体数据做了什么修改。
对于这类需求,事件驱动模型也可以提供很好的解决方案,我们可以认为每次修改实体数据都是一次事件,那么在修改实体数据后将这个事件存储到事件队列中即可实现跟踪状态变化的需求。
Spring使用时间驱动
方案一(不推荐)
- 实现ApplicationListener接口,发送ApplicationEvent(ApplicationEvent通用性太大,不能具体化事件)
/**
* 1. Spring事件驱动最基础的使用 ApplicationEventPublisher、ApplicationEvent、ApplicationListener
* 2. ApplicationEventPublisher 子类 ApplicationContext
* 3. 事件源、监听器 需要被spring管理
* 4. 监听器 需要实现 ApplicationListener<ApplicationEvent>
* 5. 可体现事件源和监听器之间的松耦合 仅依赖spring、ApplicationEvent
* @author dafeng
*/
@Slf4j
@SpringBootApplication
public class Demo1App implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Demo1App.class, args);
}
@Autowired
ApplicationEventPublisher applicationEventPublisher;
// @Autowired
// ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) {
applicationEventPublisher.publishEvent(new ApplicationEvent(this) {
});
}
}
/**
* @author dafeng
*/
@Slf4j
@Component
public class Demo1Listener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
log.info("[onApplicationEvent]: {}", applicationEvent.toString());
}
}
方案二(还不完美)
- 自定义事件继承ApplicationEvent
- 发送 自定义事件
- 监听 自定义事件
/**
* 自定义事件 Demo2Event 继承 ApplicationEvent
* 实现对指定类型事件进行监听
* @author dafeng
*/
@Slf4j
@SpringBootApplication
public class Demo2App implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Demo2App.class, args);
}
@Autowired
ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) {
applicationContext.publishEvent(new Demo2Event(this));
}
}
/**
* @author dafeng
*/
public class Demo2Event extends ApplicationEvent {
public Demo2Event(Object source) {
super(source);
}
@Override
public String toString() {
return "Demo2Event{" + "source=" + source + '}';
}
}
/**
* @author dafeng
*/
@Slf4j
@Component
public class Demo2Listener implements ApplicationListener<Demo2Event> {
@Override
public void onApplicationEvent(Demo2Event demo2Event) {
log.info("[onApplicationEvent]: {}", demo2Event.toString());
}
}
方案三(最终方案)
- 自定义事件,并且添加业务参数
- 事件源一般很少用到,是可以不传的
- 使用 @EventListener 替换 ApplicationListener
- 去除继承ApplicationEvent
/**
* 1. 自定义事件 Demo3Event 添加业务参数
* 2. 忽略事件源 根据实际业务情况而定 减少参数
* 3. 使用 @EventListener 替换 implements ApplicationListener<Demo2Event> 增加监听者的可扩展性
* @author dafeng
*/
@Slf4j
@SpringBootApplication
public class Demo3App implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Demo3App.class, args);
}
@Autowired
ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) {
String orderId = "order-001";
applicationContext.publishEvent(new Demo3Event(this, orderId));
}
}
/**
* @author dafeng
*/
public class Demo3Event {
private String orderId;
public Demo3Event(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
@Override
public String toString() {
return "Demo3Event{" + "orderId='" + orderId + '\'' + ", source=" + source + '}';
}
}
/**
* @author dafeng
*/
@Slf4j
@Component
public class Demo3Listener {
@EventListener
public void listener001(ApplicationEvent event) {
log.info("[listener001] >> {}", event.toString());
}
@EventListener
public void listener002(Demo3Event event) {
log.info("[listener002] >> {}", event.toString());
}
}
方案四(最终方案-同步)
大家要知道,发布时间和监听消费这个过程是同步的,因此你会很惊讶,那为什么我不把代码直接写,而要搞一个事件。你这样考虑是对的,但是你忽略了一个点,事件本身具有【解耦、通用性】的功能,如果说你等下要做的这件事,多个地方会用到具有通用性是可以抽成一个事件的
那如果你发布了一个事件,但是有两个监听者,并且需要A监听者完成后,才能进行B监听者,那么通过@Order可以处理,Order值越小越优先
/**
* @author dafeng
*/
@Slf4j
@Component
public class Demo5Listener {
// @Async
@Order(1)
@EventListener
public void listener001(Demo5Event event) {
log.info("[listener001] >> {}", event.toString());
ThreadUtil.sleep(2000);
}
// @Async
@Order(2)
@EventListener
public void listener002(Demo5Event event) {
log.info("[listener002] >> {}", event.toString());
ThreadUtil.sleep(2000);
}
}
方案四(最终方案-异步)
这时有人问了,我这东西也不通用啊,解不解耦无所谓,那不就没用了吗?此言差矣,如果你要进行多个操作之间没有先后顺序,也没有原子性,谁先进行、谁先完成、按不按顺序都无所谓,那么异步是最好的了
/**
* @author dafeng
*/
@Slf4j
@Component
public class Demo5Listener {
@Async
@EventListener
public void listener001(Demo5Event event) {
log.info("[listener001] >> {}", event.toString());
ThreadUtil.sleep(2000);
}
}
什么情况下使用时间驱动?
- 具有通用性
- 可以异步处理
- 通用性 & 异步处理