高效推送!Spring Boot 3.4 實現(xiàn)網(wǎng)頁消息通知的五種最佳方案
在現(xiàn)代應(yīng)用開發(fā)中,實時消息推送已成為提升用戶體驗的重要手段。無論是在線聊天、系統(tǒng)通知、金融數(shù)據(jù)更新,還是團隊協(xié)作,服務(wù)器主動向瀏覽器推送信息的能力至關(guān)重要。本文將詳細介紹 Spring Boot 3.4 中實現(xiàn)網(wǎng)頁消息推送的五種主流方案,幫助開發(fā)者選擇最適合的技術(shù)方案。
為什么需要消息推送?
傳統(tǒng)的 HTTP 請求是典型的客戶端-服務(wù)器交互模式,即客戶端發(fā)起請求,服務(wù)器返回響應(yīng)。然而,在許多業(yè)務(wù)場景下,我們希望服務(wù)器能夠主動向客戶端推送消息,例如:
- 在線聊天系統(tǒng)
- 股票、基金等金融數(shù)據(jù)的實時更新
- 業(yè)務(wù)系統(tǒng)的通知提醒
- 在線文檔的協(xié)同編輯
- ......
消息推送的五種方案
1. 短輪詢(Short Polling)
工作原理
客戶端定期向服務(wù)器發(fā)送請求,檢查是否有新消息。
Spring Boot 3.4 實現(xiàn)
package com.icoderoad.controller;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/messages")
public class MessageController {
private final Map<String, List<String>> userMessages = new ConcurrentHashMap<>();
@GetMapping("/{userId}")
public List<String> getMessages(@PathVariable String userId) {
List<String> messages = userMessages.getOrDefault(userId, new ArrayList<>());
List<String> result = new ArrayList<>(messages);
messages.clear(); // 清空已讀消息
return result;
}
@PostMapping("/{userId}")
public void sendMessage(@PathVariable String userId, @RequestBody String message) {
userMessages.computeIfAbsent(userId, k -> new ArrayList<>()).add(message);
}
}
前端代碼
function startPolling(){
setInterval(()=>{
fetch('/api/messages/user123')
.then(response=> response.json())
.then(messages=>{
if(messages.length>0){
messages.forEach(msg=>console.log(msg));
}
});
},3000);// 每 3 秒查詢一次
}
優(yōu)缺點
優(yōu)點:
- 實現(xiàn)簡單,適用于大部分瀏覽器
- 兼容性極好,無需特殊服務(wù)器配置
缺點:
- 資源消耗大,存在大量無效請求
- 實時性較差,受輪詢間隔影響
- 服務(wù)器負載高,在用戶量大時不適用
2. 長輪詢(Long Polling)
工作原理
客戶端發(fā)起請求,若服務(wù)器無新消息,則保持連接打開,直到有消息或超時。
Spring Boot 3.4 實現(xiàn)
package com.icoderoad.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/long-polling")
public class LongPollingController {
private final Map<String, DeferredResult<List<String>>> waitingRequests = new ConcurrentHashMap<>();
private final Map<String, List<String>> pendingMessages = new ConcurrentHashMap<>();
@GetMapping("/{userId}")
public DeferredResult<List<String>> waitForMessages(@PathVariable String userId) {
DeferredResult<List<String>> result = new DeferredResult<>(60000L, new ArrayList<>());
List<String> messages = pendingMessages.get(userId);
if (messages != null && !messages.isEmpty()) {
List<String> messagesToSend = new ArrayList<>(messages);
messages.clear();
result.setResult(messagesToSend);
} else {
waitingRequests.put(userId, result);
result.onCompletion(() -> waitingRequests.remove(userId));
result.onTimeout(() -> waitingRequests.remove(userId));
}
return result;
}
}
優(yōu)缺點
優(yōu)點:
- 比短輪詢更高效,減少無效請求
- 近實時響應(yīng)
缺點:
- 服務(wù)器資源占用較大
- 不適用于大規(guī)模并發(fā)請求
3. Server-Sent Events(SSE)
工作原理
服務(wù)器通過單向流向客戶端推送數(shù)據(jù)。
Spring Boot 3.4 實現(xiàn)
package com.icoderoad.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/sse")
public class SSEController {
private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
@GetMapping("/subscribe/{userId}")
public SseEmitter subscribe(@PathVariable String userId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitters.put(userId, emitter);
return emitter;
}
@PostMapping("/publish/{userId}")
public void publish(@PathVariable String userId, @RequestBody String message) throws IOException {
SseEmitter emitter = emitters.get(userId);
if (emitter != null) {
emitter.send(SseEmitter.event().name("MESSAGE").data(message));
}
}
}
優(yōu)缺點
優(yōu)點:
- 服務(wù)器主動推送,減少客戶端請求
- 自動重連
缺點:
- 僅支持單向通信
- 不支持 IE 瀏覽器
4. WebSocket
工作原理
WebSocket 允許服務(wù)器與客戶端建立雙向連接。
Spring Boot 3.4 實現(xiàn)
package com.icoderoad.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MessageWebSocketHandler(), "/ws/messages").setAllowedOrigins("*");
}
}
優(yōu)缺點
優(yōu)點:
- 全雙工通信,實時性最強
- 適用于高頻交互場景
缺點:
- 需要瀏覽器和服務(wù)器都支持 WebSocket
- 可能需要負載均衡支持
5.基于 MQTT 的消息推送
工作原理
MQTT 是一種輕量級的消息傳輸協(xié)議,基于 發(fā)布/訂閱 機制,適用于低帶寬、高延遲或不穩(wěn)定的網(wǎng)絡(luò)環(huán)境。服務(wù)器(Broker)負責(zé)消息的轉(zhuǎn)發(fā),客戶端可以訂閱特定的主題(Topic),當(dāng)有新消息發(fā)布時,Broker 會自動推送給所有訂閱者。
Spring Boot 3.4 + MQTT 實現(xiàn)
- 引入依賴
在 pom.xml
中添加 Eclipse Paho MQTT 客戶端:
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
- 配置 MQTT 連接
在 application.yml
添加 MQTT 服務(wù)器的配置信息:
mqtt:
broker: tcp://localhost:1883
clientId: spring-boot-mqtt-client
topic: /notifications
username: admin
password: secret
- 編寫 MQTT 配置類
package com.icoderoad.config;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Component;
@Component
public class MqttConfig {
private MqttClient client;
public MqttConfig() throws MqttException {
String brokerUrl = "tcp://localhost:1883";
String clientId = "spring-boot-mqtt-client";
client = new MqttClient(brokerUrl, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setUserName("admin");
options.setPassword("secret".toCharArray());
client.connect(options);
}
public void publishMessage(String topic, String message) throws MqttException {
client.publish(topic, message.getBytes(), 2, false);
}
}
- 消息發(fā)布接口
package com.icoderoad.controller;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/mqtt")
public class MqttController {
private final MqttConfig mqttConfig;
public MqttController(MqttConfig mqttConfig) {
this.mqttConfig = mqttConfig;
}
@PostMapping("/publish")
public String publishMessage(@RequestParam String topic, @RequestBody String message) {
try {
mqttConfig.publishMessage(topic, message);
return "消息已發(fā)送";
} catch (MqttException e) {
return "發(fā)送失?。? + e.getMessage();
}
}
}
- 客戶端訂閱 MQTT 消息
前端 JavaScript 代碼(使用 mqtt.js
):
const mqtt =require('mqtt');
const client = mqtt.connect('ws://localhost:9001');
client.on('connect',()=>{
console.log('已連接到 MQTT Broker');
client.subscribe('/notifications',(err)=>{
if(!err){
console.log('成功訂閱 /notifications 主題');
}
});
});
client.on('message',(topic, message)=>{
console.log(`收到消息: ${message.toString()}`);
});
MQTT 方案的優(yōu)缺點
優(yōu)點:
- 高效基于發(fā)布/訂閱模式,支持大規(guī)模并發(fā)推送,性能優(yōu)越。
- 輕量協(xié)議數(shù)據(jù)包小,適用于 IoT 和移動端推送。
- 穩(wěn)定性強即使客戶端掉線,MQTT 也支持 QoS 質(zhì)量保證,可以保證消息傳遞。
缺點:
- 服務(wù)器需要額外部署 MQTT Broker(如 Mosquitto)
- 不適用于短連接場景(如一次性通知)。
結(jié)論
不同方案適用于不同場景,開發(fā)者應(yīng)根據(jù)業(yè)務(wù)需求選擇合適的技術(shù)方案。在 Spring Boot 3.4 中,WebSocket 是最實時的方案,而 SSE 適用于單向推送,長輪詢和短輪詢則適用于兼容性要求較高的場景。