以下是一個完整的基于 Spring Boot 的 Server-Sent Events (SSE) 示例,包括服務端和客戶端的實現。
一、服務端實現
1. 創建 Spring Boot 項目
首先,創建一個基本的 Spring Boot 項目,并添加 spring-boot-starter-web
依賴。在 pom.xml
中添加以下內容:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
2. 創建 SSE 控制器
創建一個控制器來處理 SSE 連接并推送實時消息。
SseController.java
package com.example.sse;import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;@RestController
public class SseController {private final ExecutorService executorService = Executors.newCachedThreadPool();@GetMapping("/sse")public SseEmitter handleSse() {SseEmitter emitter = new SseEmitter();executorService.execute(() -> {try {for (int i = 0; i < 10; i++) {emitter.send("Message " + i, MediaType.TEXT_PLAIN);TimeUnit.SECONDS.sleep(1);}emitter.complete();} catch (IOException | InterruptedException e) {emitter.completeWithError(e);}});return emitter;}
}
3. 配置跨域(可選)
如果前端和后端運行在不同端口上,需要配置跨域。
CorsConfig.java
package com.example.sse;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "POST", "PUT", "DELETE").allowedHeaders("*").allowCredentials(true);}
}
二、客戶端實現
在前端頁面中,使用 EventSource
來訂閱 SSE。
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>SSE Example</title>
</head>
<body><h1>Server-Sent Events Example</h1><div id="events"></div><script>const eventSource = new EventSource('/sse');eventSource.onmessage = function(event) {const newElement = document.createElement("div");newElement.innerHTML = "Message: " + event.data;document.getElementById("events").appendChild(newElement);};eventSource.onerror = function(event) {eventSource.close();alert("EventSource failed: " + event);};</script>
</body>
</html>
三、運行和測試
- 啟動 Spring Boot 應用。
- 在瀏覽器中訪問
http://localhost:8080
,即可看到服務端每秒推送的消息。
四、擴展功能
1. 動態推送消息
可以通過維護一個 SseEmitter
的映射來動態推送消息。
SseController.java(動態推送版本)
package com.example.sse;import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@RestController
public class SseController {private final Map<String, SseEmitter> emitterMap = new ConcurrentHashMap<>();@GetMapping("/sse/{userId}")public SseEmitter connect(@PathVariable String userId) {SseEmitter emitter = new SseEmitter();emitterMap.put(userId, emitter);emitter.onCompletion(() -> emitterMap.remove(userId));emitter.onTimeout(() -> emitterMap.remove(userId));emitter.onError(e -> emitterMap.remove(userId));return emitter;}@GetMapping("/push/{userId}")public void push(@PathVariable String userId, @RequestParam String message) {SseEmitter emitter = emitterMap.get(userId);if (emitter != null) {try {emitter.send(message);} catch (IOException e) {emitter.completeWithError(e);emitterMap.remove(userId);}}}
}
2. 使用 WebFlux 實現 SSE
如果需要更高效的響應式編程支持,可以使用 Spring WebFlux。
SseController.java(WebFlux 版本)
package com.example.sse;import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.time.Duration;@RestController
public class SseController {@GetMapping("/sse/stream")public Flux<ServerSentEvent<String>> streamSse() {return Flux.interval(Duration.ofSeconds(1)).map(sequence -> ServerSentEvent.<String>builder().id(String.valueOf(sequence)).event("periodic-event").data("Current time: " + java.time.LocalTime.now()).build());}
}
通過以上步驟,你可以實現一個完整的基于 Spring Boot 的 SSE 應用。