目录
- SSE简介
- SSE原理
- SSE的使用场景
- [在Spring Boot中集成SSE](#在Spring Boot中集成SSE)
- [创建Spring Boot项目](#创建Spring Boot项目)
- 添加依赖
- 创建SSE控制器
- 前端使用SSE
- 详细案例:股票价格实时推送
- 总结
SSE简介
服务器发送事件(Server-Sent Events,SSE)是一种在HTTP协议上实现的服务器推送技术。它允许服务器单向地将实时更新推送到客户端。与WebSocket不同,SSE是基于HTTP协议的简化实现,非常适合需要从服务器向客户端单向推送数据的场景。
SSE原理
SSE通过HTTP协议的一个长连接来实现服务器到客户端的单向数据流。客户端通过发送一个普通的HTTP请求来建立连接,服务器接收到请求后,保持连接不断开,并通过这个连接持续地发送事件。客户端使用JavaScript的EventSource API来处理这些事件。
SSE的使用场景
SSE适用于以下应用场景:
- 实时通知:如邮件通知、系统消息推送。
- 实时更新:如股票行情、新闻更新。
- 监控和仪表盘:实时监控数据的展示。
- 社交媒体更新:如实时评论和点赞。
在Spring Boot中集成SSE
创建Spring Boot项目
首先,使用Spring Initializr或IDE(如IntelliJ IDEA)创建一个新的Spring Boot项目,选择合适的Spring Boot版本(如2.5.x或3.x),并添加以下依赖:
- Spring Web
添加依赖
在pom.xml
中添加Spring Web的依赖:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 其他依赖 -->
</dependencies>
创建SSE控制器
创建一个控制器来处理SSE连接和事件推送。
java
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.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@RestController
public class SseController {
@GetMapping("/sse")
public SseEmitter handleSse() {
// 创建一个新的SseEmitter实例,超时时间为30秒
SseEmitter emitter = new SseEmitter(30_000L);
// 创建一个ScheduledExecutorService来定时发送事件
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
// 每秒发送一个当前时间的消息
executor.scheduleAtFixedRate(() -> {
try {
// 发送事件,事件名称为"message",数据为当前时间戳
emitter.send(SseEmitter.event().name("message").data("Current Time: " + System.currentTimeMillis()));
} catch (IOException e) {
// 发送失败时完成该Emitter
emitter.completeWithError(e);
}
}, 0, 1, TimeUnit.SECONDS);
// 在30秒后完成该Emitter
executor.schedule(() -> emitter.complete(), 30, TimeUnit.SECONDS);
return emitter;
}
}
前端使用SSE
在前端使用JavaScript的EventSource来接收服务器发送的事件:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSE Demo</title>
</head>
<body>
<h1>SSE Demo</h1>
<div id="messages"></div>
<script>
// 创建一个新的EventSource实例,连接到服务器的/sse端点
const eventSource = new EventSource("/sse");
// 当收到服务器发送的消息时,执行此函数
eventSource.onmessage = function(event) {
// 获取消息展示的div
const messagesDiv = document.getElementById("messages");
// 创建一个新的div元素来展示新消息
const newMessage = document.createElement("div");
newMessage.textContent = event.data; // 设置div的文本内容为事件数据
messagesDiv.appendChild(newMessage); // 将新消息添加到消息展示div中
};
// 当发生错误时,执行此函数
eventSource.onerror = function(error) {
console.error("EventSource failed: ", error);
eventSource.close(); // 关闭EventSource
};
</script>
</body>
</html>
详细案例:股票价格实时推送
假设我们需要实现一个股票价格实时推送的功能,服务器定期向客户端发送股票价格更新。
创建StockService类
模拟股票价格变化的服务类。
java
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class StockService {
private Random random = new Random();
// 模拟获取股票价格的方法
public double getStockPrice(String symbol) {
// 返回一个随机价格
return 100 + (random.nextDouble() * 50);
}
}
更新SseController类
使用StockService类来推送股票价格。
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@RestController
public class SseController {
private final StockService stockService;
// 使用构造器注入StockService
public SseController(StockService stockService) {
this.stockService = stockService;
}
@GetMapping("/sse/{symbol}")
public SseEmitter handleSse(@PathVariable String symbol) {
// 创建一个新的SseEmitter实例,超时时间为30秒
SseEmitter emitter = new SseEmitter(30_000L);
// 创建一个ScheduledExecutorService来定时发送事件
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
// 每秒发送一个股票价格更新
executor.scheduleAtFixedRate(() -> {
try {
// 获取股票价格
double price = stockService.getStockPrice(symbol);
// 发送事件,事件名称为"stock-price",数据为股票价格
emitter.send(SseEmitter.event().name("stock-price").data("Stock Price of " + symbol + ": " + price));
} catch (IOException e) {
// 发送失败时完成该Emitter
emitter.completeWithError(e);
}
}, 0, 1, TimeUnit.SECONDS);
// 在30秒后完成该Emitter
executor.schedule(() -> emitter.complete(), 30, TimeUnit.SECONDS);
return emitter;
}
}
更新前端代码
在前端展示股票价格更新。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stock Price SSE Demo</title>
</head>
<body>
<h1>Stock Price SSE Demo</h1>
<input type="text" id="symbol" placeholder="Enter stock symbol">
<button onclick="connect()">Connect</button>
<div id="messages"></div>
<script>
let eventSource;
function connect() {
const symbol = document.getElementById("symbol").value;
if (eventSource) {
eventSource.close(); // 关闭已有的连接
}
// 创建一个新的EventSource实例,连接到服务器的/sse/{symbol}端点
eventSource = new EventSource("/sse/" + symbol);
// 当收到服务器发送的消息时,执行此函数
eventSource.onmessage = function(event) {
// 获取消息展示的div
const messagesDiv = document.getElementById("messages");
// 创建一个新的div元素来展示新消息
const newMessage = document.createElement("div");
newMessage.textContent = event.data; // 设置div的文本内容为事件数据
messagesDiv.appendChild(newMessage); // 将新消息添加到消息展示div中
};
// 当发生错误时,执行此函数
eventSource.onerror = function(error) {
console.error("EventSource failed: ", error);
eventSource.close(); // 关闭EventSource
};
}
</script>
</body>
</html>