在 Java 中,使用 Server-Sent Events (SSE) 来实现流式响应,允许服务器实时地将数据推送到客户端,适用于如实时通知、数据流、实时更新等场景。SSE 是基于 HTTP 协议的,只需要一个单向连接,服务器向客户端推送事件。

SSE (Server-Sent Events) 概述

  • 单向通信:SSE 是客户端通过 HTTP 请求向服务器发起连接,而服务器通过该连接向客户端推送事件数据。
  • 自动重连:如果连接断开,浏览器会自动尝试重新连接。
  • 数据格式:SSE 使用基于文本的格式(例如 JSON 或普通文本)来发送事件数据。

在 Java 中实现 SSE 流式响应

使用 Servlet 或 Spring Boot 都可以实现 SSE 流式响应。下面分别介绍两种方法。


1. 使用 Servlet 实现 SSE 流式响应

1.1 创建一个 Servlet 来实现 SSE

以下是一个基于 Servlet 的示例,展示如何向客户端推送事件数据。

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.concurrent.*;

public class SSEServlet extends HttpServlet {
    // 使用 ExecutorService 来模拟异步任务
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置响应类型为 SSE
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");

        // 设置流式响应
        PrintWriter writer = response.getWriter();

        // 模拟向客户端发送 SSE 数据
        executorService.submit(() -> {
            try {
                int i = 0;
                while (true) {
                    // 模拟事件数据
                    writer.write("data: Event number " + i + "\n\n");
                    writer.flush();

                    // 每隔 2 秒发送一次事件
                    Thread.sleep(2000);
                    i++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    @Override
    public void destroy() {
        super.destroy();
        executorService.shutdown();
    }
}

1.2 配置 web.xml

确保在 web.xml 中正确配置该 Servlet。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                             http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <servlet>
        <servlet-name>SSEServlet</servlet-name>
        <servlet-class>SSEServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <url-pattern>/events</url-pattern>
    </servlet-mapping>
</web-app>

1.3 客户端实现

客户端可以使用 JavaScript 接收 SSE 事件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE Example</title>
</head>
<body>
    <h1>Server-Sent Events Example</h1>
    <div id="events"></div>

    <script type="text/javascript">
        // 建立与服务器的 SSE 连接
        var eventSource = new EventSource("/events");

        // 监听来自服务器的消息
        eventSource.onmessage = function(event) {
            var newElement = document.createElement("div");
            newElement.textContent = "Received: " + event.data;
            document.getElementById("events").appendChild(newElement);
        };

        // 监听连接打开事件
        eventSource.onopen = function(event) {
            console.log("Connection to server opened.");
        };

        // 监听错误事件
        eventSource.onerror = function(event) {
            console.error("Error occurred:", event);
        };
    </script>
</body>
</html>

在上述代码中,客户端通过 EventSource 来连接 /events 路径,服务器则会向客户端推送事件。


2. 使用 Spring Boot 实现 SSE 流式响应

在 Spring Boot 中,使用 @GetMapping 来实现流式响应也非常简单。Spring 提供了非常方便的支持来处理 SSE。

2.1 创建一个 Spring Boot Controller 来实现 SSE

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.http.ResponseEntity;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;

@Controller
@RequestMapping("/events")
public class SSEController {

    @GetMapping
    public ResponseEntity<SseEmitter> stream() {
        // 创建一个 SseEmitter 对象
        SseEmitter emitter = new SseEmitter();

        // 模拟向客户端发送 SSE 数据
        new Thread(() -> {
            try {
                int i = 0;
                while (true) {
                    emitter.send(SseEmitter.event().name("message").data("Event number " + i));
                    i++;
                    Thread.sleep(2000);
                }
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        return ResponseEntity.ok()
                             .contentType(MediaType.TEXT_EVENT_STREAM)
                             .body(emitter);
    }
}

2.2 Spring Boot 应用主类

确保你有一个 Spring Boot 应用的主类来启动应用。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SseApplication {
    public static void main(String[] args) {
        SpringApplication.run(SseApplication.class, args);
    }
}

2.3 客户端实现

与 Servlet 示例中的客户端实现一样,Spring Boot 也可以通过 JavaScript 使用 EventSource 来接收事件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE Example</title>
</head>
<body>
    <h1>Server-Sent Events Example</h1>
    <div id="events"></div>

    <script type="text/javascript">
        // 建立与服务器的 SSE 连接
        var eventSource = new EventSource("/events");

        // 监听来自服务器的消息
        eventSource.onmessage = function(event) {
            var newElement = document.createElement("div");
            newElement.textContent = "Received: " + event.data;
            document.getElementById("events").appendChild(newElement);
        };

        // 监听连接打开事件
        eventSource.onopen = function(event) {
            console.log("Connection to server opened.");
        };

        // 监听错误事件
        eventSource.onerror = function(event) {
            console.error("Error occurred:", event);
        };
    </script>
</body>
</html>

总结:

  • Servlet 实现 SSE:通过设置 Content-Type 为 text/event-stream,并使用 PrintWriter 流式发送数据。
  • Spring Boot 实现 SSE:使用 SseEmitter 来异步地向客户端发送事件,并使用 MediaType.TEXT_EVENT_STREAM 设置响应类型。
  • 客户端接收 SSE:使用 EventSource 对象连接到 SSE 端点,并处理接收到的事件。

SSE 是一个非常适合实时数据推送的技术,广泛应用于实时通知、股票行情、游戏消息等场景。希望这个示例能帮助你快速上手 SSE,如果有任何问题,随时告诉我!