Spring 事件监听机制

素颜马尾好姑娘i 2024-04-03 08:41 250阅读 0赞

目录

  • 利用Spring事件机制完成需求
    • 环境准备
    • 代码
  • Spring发布异步事件

假设现在有这么一个业务场景:用户在某购物软件下单成功后,平台要发送短信通知用户下单成功。我们最直观的想法是直接在 order() 方法中添加发送短信的业务代码:

  1. public void order(){
  2. // 下单操作
  3. log.info("下单成功, 订单号:{}", orderNumber);
  4. // 发送短信
  5. sendSms();
  6. }
  7. public void sendSms(){
  8. // ...
  9. }

这样做没什么不妥,但是随着时间推移,上面的代码就会暴露出局限性:

一个月后,该商城搞了自建物流体系,用户下单成功后,还需要通知物流系统发货,于是你又要打开 OrderService 修改 order() 方法:

  1. public void order(){
  2. // 下单成功
  3. log.info("下单成功, 订单号:{}", orderNumber);
  4. // 发送短信
  5. sendSms();
  6. // 通知车队发货
  7. notifyCar();
  8. }

嗯,nice。

又过了一段时间,老板被抓了,股价暴跌,于是老板决定卖掉自己的车队,所以下单后就不用通知车队了。

重新修改 OrderService:

  1. public void order(){
  2. // 下单成功
  3. log.info("下单成功, 订单号:{}", orderNumber);
  4. // 发送短信
  5. sendSms();
  6. // 车队没了,注释掉这行代码
  7. // notifyCar();
  8. }

又过了一段时间,老板荣耀归来,东山再起,又把车队重现组建了起来。

  1. public void order(){
  2. // 下单成功
  3. log.info("下单成功, 订单号:{}", orderNumber);
  4. // 发送短信
  5. sendSms();
  6. // 车队买回来了,放开这段代码
  7. notifyCar()
  8. }

车队回来了,你却受不了这大起大落异常刺激的生活,决定离职。

就在这时候,组长拉住了你,语重心长地和你说:小伙子,知道什么叫 “以增量的方式应对变化的需求” 吗?听过Spring事件监听机制吗?

说时迟那时快,组长拿来一支笔和一张纸,唰唰唰画了一张图:
在这里插入图片描述

利用Spring事件机制完成需求

环境准备

创建一个 SpringBoot 项目即可。
在这里插入图片描述

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.projectlombok</groupId>
  8. <artifactId>lombok</artifactId>
  9. <optional>true</optional>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-test</artifactId>
  14. <scope>test</scope>
  15. </dependency>
  16. </dependencies>

代码

OrderService

  1. /**
  2. * 订单服务
  3. */
  4. @Service
  5. public class OrderService {
  6. @Autowired
  7. private ApplicationContext applicationContext;
  8. public void order() {
  9. // 下单成功
  10. System.out.println("下单成功...");
  11. // 发布通知(传入了当前对象)
  12. applicationContext.publishEvent(new OrderSuccessEvent(this));
  13. System.out.println("main线程结束...");
  14. }
  15. }

OrderSuccessEvent(继承ApplicationEvent,自定义事件)

  1. public class OrderSuccessEvent extends ApplicationEvent {
  2. public OrderSuccessEvent(Object source) {
  3. super(source);
  4. }
  5. }

SmsService(实现ApplicationListener,监听OrderSuccessEvent)

  1. /**
  2. * 短信服务,监听OrderSuccessEvent
  3. */
  4. @Service
  5. public class SmsService implements ApplicationListener<OrderSuccessEvent> {
  6. @Override
  7. public void onApplicationEvent(OrderSuccessEvent event) {
  8. this.sendSms();
  9. }
  10. /**
  11. * 发送短信
  12. */
  13. public void sendSms() {
  14. System.out.println("发送短信...");
  15. }
  16. }

SpringEventApplicationTests

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. class SpringEventApplicationTests {
  4. @Autowired
  5. private OrderService orderService;
  6. @Test
  7. public void testSpringEvent() {
  8. orderService.order();
  9. }
  10. }

输出

  1. 下单成功...
  2. 发送短信...
  3. main线程结束...

流程示意图
在这里插入图片描述
如果后期针对下单成功有新的操作,可以新写一个事件监听类:

  1. /**
  2. * 物流服务
  3. */
  4. @Service
  5. public class CarService implements ApplicationListener<OrderSuccessEvent> {
  6. @Override
  7. public void onApplicationEvent(OrderSuccessEvent event) {
  8. this.dispatch();
  9. }
  10. public void dispatch() {
  11. System.out.println("发车咯...");
  12. }
  13. }

在这里插入图片描述
这就是以增量的方式应对变化的需求,而不是去修改已有的代码(ServiceA)。同样的,对老项目改造时也是如此,如果你不知道原来的接口是干嘛的,最好不要去动它,宁愿新写一个接口,即一般提倡“对扩展开放,对修改关闭”。

上面 SmsService 既是一个服务,还是一个 Listener,因为它既有 @Service 又实现了 ApplicationListener 接口。

但是仅仅是为了一个监听回调方法而实现一个接口,未免麻烦,所以 Spring 提供了注解的方式:

  1. /**
  2. * 短信服务,监听OrderSuccessEvent,但不用实现ApplicationListener
  3. */
  4. @Service
  5. public class SmsService {
  6. /**
  7. * 发送短信 @EventListener指定监听的事件
  8. */
  9. @EventListener(OrderSuccessEvent.class)
  10. public void sendSms() {
  11. try {
  12. Thread.sleep(1000L * 5);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println("发送短信...");
  17. }
  18. }

Spring发布异步事件

看似很完美了,但是你注意到 Spring 默认的事件机制是同步的:
在这里插入图片描述如果针对OrderService 下单成功的操作越来越多,比如下单后需要完成的对应操作有十几个,那么等十几个其他服务都调用完毕,时间会很长。
在这里插入图片描述
所以,你必须想办法把 Spring 的事件机制改成异步的,尽可能快地返回下单的结果本身,而不是等其他附属服务全部完成(涉及到其他问题暂时按下不表)。

要想把 Spring 事件机制改造成异步通知,最粗暴的方法是:
OrderService

  1. /**
  2. * 订单服务
  3. */
  4. @Service
  5. public class OrderService {
  6. @Autowired
  7. private ApplicationContext applicationContext;
  8. public void order() {
  9. // 下单成功
  10. System.out.println("下单成功...");
  11. // 发布通知
  12. new Thread(() ->{
  13. applicationContext.publishEvent(new OrderSuccessEvent(this));
  14. }).start();
  15. System.out.println("main线程结束...");
  16. // 等SmsService结束
  17. try {
  18. Thread.sleep(1000L * 5);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. }

SmsService

  1. /**
  2. * 短信服务,监听OrderSuccessEvent
  3. */
  4. @Service
  5. public class SmsService implements ApplicationListener<OrderSuccessEvent> {
  6. @Override
  7. public void onApplicationEvent(OrderSuccessEvent event) {
  8. this.sendSms();
  9. }
  10. /**
  11. * 发送短信
  12. */
  13. public void sendSms() {
  14. try {
  15. Thread.sleep(1000L * 3);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println("发送短信...");
  20. }
  21. }

输出

  1. 下单成功...
  2. main线程结束...
  3. 发送短信.

在这里插入图片描述
当然,这种做法其实违背了 Spring 事件机制的设计初衷。人家会想不到你要搞异步通知?

把OrderService改回来:

  1. @Service
  2. public class OrderService {
  3. @Autowired
  4. private ApplicationContext applicationContext;
  5. public void order() {
  6. // 下单成功
  7. System.out.println("下单成功...");
  8. // 发布通知
  9. applicationContext.publishEvent(new OrderSuccessEvent(this));
  10. System.out.println("main线程结束...");
  11. // 等SmsService结束
  12. try {
  13. Thread.sleep(1000L * 5);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }

此时仍然是异步的。

Spring 事件机制适合单体应用,同一个 JVM 且并发不大的情况,如果是分布式应用,推荐使用MQ。

Spring 事件监听机制和 MQ 有相似的地方,也有不同的地方。MQ 允许跨 JVM,因为它本身是独立于项目之外的,切提供了消息持久化的特性,而 Spring 事件机制哪怕使用了异步,本质是还是一种方法调用,宕机了就没了。

发表评论

表情:
评论列表 (有 0 条评论,250人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring事件监听机制

    前言 Spring中的事件机制其实就是设计模式中的观察者模式,主要由以下角色构成: 1. 事件 2. 事件监听器(监听并处理事件) 3. 事件发布者(发布事件...

    相关 事件监听机制

    ![这里写图片描述][20160420164724788]   Java中的事件监听是整个Java消息传递的基础和关键。牵涉到三类对象:事件源(Event S