阅读 604

Spring Boot之事件监听机制 | 小册免费学

当一个主业务执行后,不希望附加业务影响到主业务的执行,或者当一个主业务执行时,会触发多个附加业务的执行,且附加业务是不确定的?这时我们应该怎么处理呢?

最直观的想法就是在主业务后面直接编写附加业务,但是这样会带来什么缺点呢?可扩展性太差,需要频繁修改主业务的类,耦合性太高。

以增量的方法应对变化的需求:

当主业务执行时,不需要直接去调用各种附加的业务操作,只需要发送一个消息即可。附加操作的业务接受到这个消息进行相应的操作。

事件监听的通俗理解

你安排别人做工作,想监听别人是否做完

1、每十秒就跑去看看,就是你说的死循环的方式,是拉的方式

2、让她做完自己过来告诉你,是推的方式。

利用Spring机制完成需求

概念

应用程序事件允许我们发送和接收特定事件,我们可以根据需要处理这些事件。事件用于在松散耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此可以在不影响发布者的情况下修改订阅者,反之亦然。

  1. 定义事件源(继承ApplicationEvent接口)
  2. 发布事件源消息(applicationContext.publishEvent)
  3. 定义监听类服务(实现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提供的,但我们并不需要实现。
本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情
文章分类
后端
文章标签