使用 Spring Boot 实现 SSE(送外卖示例)

你以前听说过 SSE(服务器发送事件)吗?它是一种通过 HTTP 协议工作的单向消息传递技术,可以从服务器向客户端发送消息。因此,每当有可用数据时,客户端都会实时接收并更新。

SSE 工作原理

SSE 具有单向通信功能,只有服务器会向客户端发送事件,这些事件由 JavaScript 中的 EventSource API 捕捉,浏览器使用该 API 监听事件(我们将在本文稍后部分详细讨论)。使用 SSE 无法发送二进制数据,只能发送消息。我们可以在天气、体育、股票交易等应用中找到一些 SSE 用例。

SSE 是唯一的选择吗?

替代 SSE 的选择不止一种,但两者没有优劣之分,这取决于您的需求。除了 SSE,你还可以找到一些替代方案,比如 WebSocket 和 Event Data Pooling,但如果你只需要从服务器获取一些接收到的数据,也许 SSE 才是最佳选择。让我来解释一下两者的区别:

  • WebSocket

WebSocket 与 SSE 稍有不同,它是一种在客户端和服务器之间进行双向通信的协议。除了信息,还可以在有效载荷中发送二进制文件。一些 WebSocket 用例包括聊天应用和多人游戏,这两种应用都需要全双工通信。

  • Event Data Pooling

与 SSE 和 WebSocket 不同的是,客户端需要向服务器请求数据,这通常会占用更多的硬件资源,因为在服务器做出响应之前,客户端会发出大量请求。

如何使用 Spring Boot 实现 SSE?

考虑到本文的重点是 SSE,我们将通过一个实践示例来说明如何在 Java 应用程序中使用 Spring Boot 框架。

我们将使用 Servlet Stack (MVC),因为它简单易懂,而且大多数开发人员都习惯使用命令式代码进行开发。不过,你也可以使用反应堆栈(WebFlux)来实现它。

让我们通过一个食品配送系统(送外卖)来了解它是如何工作的。我们要做的是向用户显示订单进度,可用的状态有:订单已下、在厨房、在路上和已送达。

考虑到这只是一个用来说明 SSE 用例的原型,系统会自动处理所有的订单步骤。

创建 SSE 入口点

首先,需要在控制器中声明一个返回 SseEmitter 接口的端点。

private final Collection<SseEmitter> emitters = new CopyOnWriteArrayList<>();

@GetMapping(path = "/order-status", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
SseEmitter orderStatus() {

   SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);

   emitter.onCompletion(() -> emitters.remove(emitter));
   emitter.onTimeout(() -> emitters.remove(emitter));
   emitter.onError(throwable -> {
      emitters.remove(emitter);
      emitter.completeWithError(throwable);
   });

   emitters.add(emitter);

   return emitter;
}

在接口签名中,我们将媒体类型设为 TEXT_EVENT_STREAM_VALUE,以便向客户端发送实时事件。我们的方法主体由一个新的 SseEmitter 组成,该 SseEmitter 添加到并发列表中,从而使广播发送成为可能。每当发生完成、错误或超时时,我们还会声明一些事件回调。

声明谁需要知道订单信息

在端点中连接用户后,我们如何确定订单已到达并需要准备?考虑到我们要在订单状态发生变化时向客户端发送事件,为什么不采用可观察模式并在每次变化后通知所有监听器呢?

让我们创建一个监听器,并注册代表每个订单状态的所有可观察对象。

@Component
public class OrderFoodListener {

    private final EventService eventService;

    private final Collection<Observer> observers;

    public OrderFoodListener(EventService eventService) {
        this.eventService = eventService;
        this.observers = List.of(
                new OrderObservable(eventService),
                new KitchenObservable(eventService),
                new OnTheWayObservable(eventService),
                new DeliveredObservable(eventService)
        );
    }

   public void notifyAll(OrderFood orderFood) {
        observers.forEach(foodObserver -> foodObserver.update(orderFood));
   }
}

让我来解释一下这里发生了什么:

  • Event Service 接口:这是我们发送事件方式的依赖反转接口(具体类应在基础架构层中声明)
  • Observers 集合: 我们希望在订单到来时通知的所有观察者。
  • 通知方法: 将调用 notifyAll 方法通知所有观察者新订单的信息。

通知客户订单

正如我们在前面的代码块中看到的,有四个观测变量,每个观测变量都有自己的业务规则。

让我向您展示一下我们将如何通知客户:

  • 订单即将到来
if (orderFood.getStatus() == ORDER_PLACED) {

   sendEvent(orderFood, "order");
}

订单观测器会检查初始状态是否为已下订单,如果是,它就会发送一个事件,事件名称为 “订单”。

  • 它将进入厨房
if (orderFood.getStatus() == FoodStatus.ORDER_PLACED) {

   orderFood.setStatus(FoodStatus.IN_THE_KITCHEN);
   sendEvent(orderFood, "kitchen");
   waitForProcess();
}

下单后,订单将被送往厨房,因此我们需要检查之前的状态是否仍为下单状态,然后进行更改,并发送包含更新状态的 “kitchen “事件。

OBS. waitForProcess()方法是一个线程睡眠声明,用于模拟厨房的持续进程。

  • 现在就看送餐员的了
if (orderFood.getStatus() == IN_THE_KITCHEN) {

   orderFood.setStatus(ON_THE_WAY);
   sendEvent(orderFood, "on-the-way");
   waitForProcess();
}

现在是通知用户已到达的时候了。

在客户端接收事件

在客户端,用户将等待订单更新。因此,让我们来实现它。

var eventSource = new EventSource("/order-status");

eventSource.addEventListener("order", (event) => renderEvent(event));

eventSource.addEventListener("kitchen", (event) => renderEvent(event));

eventSource.addEventListener("on-the-way", (event) => renderEvent(event));

eventSource.addEventListener("delivered", (event) => renderEvent(event));

创建了一个简单的静态 HTML 文件,并放置了一些 JavaScript 来监听来自服务器的所有事件并将它们打印在屏幕上。

结论

希望通过本文,你能理解 SSE 的主要概念,并了解类似技术之间的区别。此外,我们还看到了一个如何使用 Spring Boot 框架实现 SSE 的实践示例。

GitHub 看看本文使用的全部代码:https://github.com/GermanoSchneider/food-delivery-sse-app。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论