IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring 事件监听机制 -> 正文阅读

[Java知识库]Spring 事件监听机制


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

public void order(){
  // 下单操作
  log.info("下单成功, 订单号:{}", orderNumber);
  // 发送短信
  sendSms();
}

public void sendSms(){
    // ... 
}

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

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

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

嗯,nice。

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

重新修改 OrderService:

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

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

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

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

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

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

利用Spring事件机制完成需求

环境准备

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

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

代码

OrderService

/**
 * 订单服务
 */
@Service
public class OrderService {

    @Autowired
    private ApplicationContext applicationContext;

    public void order() {
        // 下单成功
        System.out.println("下单成功...");
        // 发布通知(传入了当前对象)
        applicationContext.publishEvent(new OrderSuccessEvent(this));
        System.out.println("main线程结束...");
    }
}

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

public class OrderSuccessEvent extends ApplicationEvent {

    public OrderSuccessEvent(Object source) {
        super(source);
    }
}

SmsService(实现ApplicationListener,监听OrderSuccessEvent)

/**
 * 短信服务,监听OrderSuccessEvent
 */
@Service
public class SmsService implements ApplicationListener<OrderSuccessEvent> {

    @Override
    public void onApplicationEvent(OrderSuccessEvent event) {
        this.sendSms();
    }

    /**
     * 发送短信
     */
    public void sendSms() {
        System.out.println("发送短信...");
    }
}

SpringEventApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
class SpringEventApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    public void testSpringEvent() {
        orderService.order();
    }

}

输出

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

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

/**
 * 物流服务
 */
@Service
public class CarService  implements ApplicationListener<OrderSuccessEvent> {
    @Override
    public void onApplicationEvent(OrderSuccessEvent event) {
        this.dispatch();
    }

    public void dispatch() {
        System.out.println("发车咯...");
    }
}

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

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

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

/**
 * 短信服务,监听OrderSuccessEvent,但不用实现ApplicationListener
 */
@Service
public class SmsService {

    /**
     * 发送短信 @EventListener指定监听的事件
     */
    @EventListener(OrderSuccessEvent.class)
    public void sendSms() {

        try {
            Thread.sleep(1000L * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发送短信...");
    }

}

Spring发布异步事件

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

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

/**
 * 订单服务
 */
@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();
        }
    }
}

SmsService

/**
 * 短信服务,监听OrderSuccessEvent
 */
@Service
public class SmsService implements ApplicationListener<OrderSuccessEvent> {

    @Override
    public void onApplicationEvent(OrderSuccessEvent event) {
        this.sendSms();
    }

    /**
     * 发送短信
     */
    public void sendSms() {
        try {
            Thread.sleep(1000L * 3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信...");
    }
}

输出

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

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

把OrderService改回来:

@Service
public class OrderService {

    @Autowired
    private ApplicationContext applicationContext;

    public void order() {
        // 下单成功
        System.out.println("下单成功...");
        // 发布通知
        applicationContext.publishEvent(new OrderSuccessEvent(this));
        System.out.println("main线程结束...");
        // 等SmsService结束
        try {
            Thread.sleep(1000L * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

此时仍然是异步的。

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

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:27:04  更:2022-10-08 20:30:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 11:02:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码