当一个主业务执行后,不希望附加业务影响到主业务的执行,或者当一个主业务执行时,会触发多个附加业务的执行,且附加业务是不确定的?这时我们应该怎么处理呢?
最直观的想法就是在主业务后面直接编写附加业务,但是这样会带来什么缺点呢?可扩展性太差,需要频繁修改主业务的类,耦合性太高。
以增量的方法应对变化的需求:
当主业务执行时,不需要直接去调用各种附加的业务操作,只需要发送一个消息即可。附加操作的业务接受到这个消息进行相应的操作。
事件监听的通俗理解
你安排别人做工作,想监听别人是否做完
1、每十秒就跑去看看,就是你说的死循环的方式,是拉的方式
2、让她做完自己过来告诉你,是推的方式。
利用Spring机制完成需求
概念
应用程序事件允许我们发送和接收特定事件,我们可以根据需要处理这些事件。事件用于在松散耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此可以在不影响发布者的情况下修改订阅者,反之亦然。
- 定义事件源(继承ApplicationEvent接口)
- 发布事件源消息(applicationContext.publishEvent)
- 定义监听类服务(实现ApplicationListener,或在监听的方法上使用EventListener)
在一个完整的事件体系中,除了事件和监听器以外,还应该有3个概念
1. 事件源:事件的产生者,任何一个event都必须有一个事件源;
2. 事件广播器:它是事件和事件监听器之间的桥梁,负责把事件通知给事件监听器;
3. 事件监听器注册表:就是spring框架为所有的监听器提供了一个存放的地方
public class OrderSuccessEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public OrderSuccessEvent(Object source) {
super(source);
}
}
@Service
@Slf4j
public class OrderService {
private final ApplicationContext applicationContext;
public OrderService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void order(){
log.info("下单成功了...");
applicationContext.publishEvent(new OrderSuccessEvent(this));
log.info("主线程结束...");
}
}
@Service
@Slf4j
public class SmsService implements ApplicationListener<OrderSuccessEvent> {
@Override
public void onApplicationEvent(OrderSuccessEvent event) {
this.sendMessage();
}
private void sendMessage() {
log.info("发送短信给用户");
}
}
流程示意图:为什么SmsService能监听到?
后期如果对下单成功有新的操作,可以写一个新的事件监听类即可。
这就是以增量的方式应对变化的需求,而不是去修改已有的代码。同样对老项目的改造时也是如此,如果你不知道原来的接口是干嘛的,最好不要去动它,宁愿写一个新的接口,提倡:对扩展开放,对修改关闭。
上面SmsService既是一个服务,还是一个Listenere,既有@Service又实现了ApplicationListener接口。
仅仅为了监听回调方法而实现一个接口太过麻烦,Spring提供了注解的方式:
@Service
@Slf4j
public class SmsService /*implements ApplicationListener<OrderSuccessEvent> */{
@EventListener(OrderSuccessEvent.class)
public void onApplicationEvent(OrderSuccessEvent event) {
log.info(event.toString());
this.sendMessage();
}
private void sendMessage() {
log.info("发送短信给用户");
}
}
但是我们会发现,Spring默认的事件机制是同步的,怎么办呢?
Spring发布异步事件
所以,需要Spring的事件改成异步,尽可能的返回下单的结果本身,而不是等其他附属服务完成后再获取到。
最简单的方式:
/**
* 订单服务
*/
@Service
public class OrderService {
@Autowired
private ApplicationContext applicationContext;
public void order() {
// 下单成功
System.out.println("下单成功...");
// 发布通知
new Thread(() ->{
applicationContext.publishEvent(new OrderSuccessEvent(this));
}).start();
System.out.println("main线程结束...");
// 等SmsService结束
try {
Thread.sleep(1000L * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这样的话所有的附属服务都执行同一个线程。
违背了Spring异步事件机制的初衷。Spring的异步事件机制是怎么玩的呢?
当SimpleApplicationEventMulticaster中的Exector不为null,就会执行异步通知。
@Configuration
public class AsyncThreadPoolEventConfig {
@Bean(name="applicationEventMulticaster")
public ApplicationEventMulticaster getApplicationEventMulticaster(){
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
最后说一句,Spring事件机制适合做单体应用,同一个JVN且并发不大的情况,如果是分布式应用,使用MQ。Spring事件监听机制和MQ不同点:
- MQ允许跨JVM,独立于项目之外,且提供了消息持久化
- Spring事件机制哪怕使用了异步,本质还是一种方法调用,宕机了就没了。
ApplicationEventMulticaster
事件广播器,它的作用是把Applicationcontext发布的Event广播给所有的监听器.具体的注册监听是在AbstractApplicationContext中实现的。
细节:
为什么发布事件源消息时,需要加入this呢? 这是促发事件的事件源,有时候事件监听的一方需要根据事件进行判断时,这时可能就需要事件源的本身的一些方法或其他属性进行操作时,就非常有必要。建议source尽量不为空。
/**
* Create a new {@code ApplicationEvent}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source); this.timestamp = System.currentTimeMillis();
}
总结:
- Spring中的事件监听使用的是观察者模式
- 所有事件需要继承ApplicationEvent父类
- 所有的监听器需要实现ApplicationListener接口
- 事件发布需要通过ApplicationContext中的publisherEvent方法实现
- 监听器的注册是ApplicationEventMulticaster提供的,但我们并不需要实现。