使用Spring Boot的定時WebSocket推送
1.概述
在本教程中,我們將看到如何使用WebSockets將計劃的消息從服務器發送到瀏覽器。一種替代方法是使用服務器發送的事件(SSE),但我們將不在本文中介紹。
Spring提供了各種調度選項。首先,我們將介紹@Scheduled
註釋。然後,我們將看到由Project Reactor提供的‘Flux::interval’方法的示例。該庫對於Webflux應用程序是開箱即用的,並且可以在任何Java項目中用作獨立庫。
此外,還存在更高級的機制,例如Quartz調度程序,但我們不會對其進行介紹。
2.一個簡單的聊天應用程序
在上一篇文章中,我們使用WebSockets構建了一個聊天應用程序。讓我們用一個新功能擴展它:聊天機器人。這些漫遊器是服務器端組件,可將計劃的消息推送到瀏覽器。
2.1。 Maven依賴
讓我們從在Maven中設置必要的依賴關係開始。要構建此項目,我們的pom.xml
應該具有:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
2.2。 JavaFaker依賴關係
我們將使用JavaFaker庫來生成機器人的消息。該庫通常用於生成測試數據。在這裡,我們將一個名為“ Chuck Norris
”的訪客添加到我們的聊天室。
讓我們看一下代碼:
Faker faker = new Faker();
ChuckNorris chuckNorris = faker.chuckNorris();
String messageFromChuck = chuckNorris.fact();
Faker將為各種數據生成器提供工廠方法。我們將使用ChuckNorris
生成器。調用chuckNorris.fact()
將顯示預定義消息列表中的隨機句子。
2.3。消息模型
聊天應用程序使用簡單的POJO作為消息包裝器:
public class OutputMessage {
private String from;
private String text;
private String time;
// standard constructors, getters/setters, equals and hashcode
}
放在一起,這是我們如何創建聊天消息的示例:
OutputMessage message = new OutputMessage(
"Chatbot 1", "Hello there!", new SimpleDateFormat("HH:mm").format(new Date())));
2.4。客戶端
我們的聊天客戶端是一個簡單的HTML頁面。它使用SockJS客戶端和STOMP消息協議。
讓我們看看客戶端如何訂閱主題:
<html>
<head>
<script src="./js/sockjs-0.3.4.js"></script>
<script src="./js/stomp.js"></script>
<script type="text/javascript">
// ...
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
// ...
stompClient.subscribe('/topic/pushmessages', function(messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
});
});
// ...
</script>
</head>
<!-- ... -->
</html>
首先,我們通過SockJS協議創建了一個Stomp客戶端。然後,主題訂閱充當服務器與連接的客戶端之間的通信通道。
在我們的存儲庫中,此代碼位於webapp/bots.html
。我們在http:// localhost:8080 / bots.html本地運行時訪問它。當然,我們需要根據部署應用程序的方式來調整主機和端口。
2.5。服務器端
在上一篇文章中,我們已經了解瞭如何在Spring中配置WebSocket。讓我們對配置進行一些修改:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// ...
registry.addEndpoint("/chatwithbots");
registry.addEndpoint("/chatwithbots").withSockJS();
}
}
為了推送消息,我們使用實用程序類SimpMessagingTemplate
。默認情況下,它在Spring Context中以@Bean
提供。我們可以看到當AbstractMessageBrokerConfiguration
在類路徑中時如何通過自動配置聲明它。因此,我們可以將其註入到任何Spring組件中。
之後,我們使用它來將消息發佈到主題/topic/pushmessages
。我們假設我們的類將那個bean注入了一個名為simpMessagingTemplate
的變量中:
simpMessagingTemplate.convertAndSend("/topic/pushmessages",
new OutputMessage("Chuck Norris", faker.chuckNorris().fact(), time));
如前面的客戶端示例所示,客戶端訂閱該主題以在消息到達時對其進行處理。
3.安排推送消息
在Spring生態系統中,我們可以從多種調度方法中進行選擇。如果我們使用Spring MVC,則@Scheduled
註釋由於其簡單性而成為自然選擇。如果我們使用Spring Webflux,我們也可以使用Project Reactor的Flux::interval
方法。我們將看到每個示例。
3.1。配置
我們的聊天機器人將使用JavaFaker的Chuck Norris生成器。我們將其配置為Bean,以便可以將其註入所需的位置。
@Configuration
class AppConfig {
@Bean
public ChuckNorris chuckNorris() {
return (new Faker()).chuckNorris();
}
}
3.2。使用@Scheduled
我們的示例機器人是預定方法。當他們運行時,他們使用SimpMessagingTemplate
通過WebSocket發送我們的OutputMessage
POJO。
顧名思義,@ @Scheduled
批註允許重複執行method 。有了它,我們可以使用基於速率的簡單調度或更複雜的“ cron”表達式。
讓我們編寫第一個聊天機器人的代碼:
@Service
public class ScheduledPushMessages {
@Scheduled(fixedRate = 5000)
public void sendMessage(SimpMessagingTemplate simpMessagingTemplate, ChuckNorris chuckNorris) {
String time = new SimpleDateFormat("HH:mm").format(new Date());
simpMessagingTemplate.convertAndSend("/topic/pushmessages",
new OutputMessage("Chuck Norris (@Scheduled)", chuckNorris().fact(), time));
}
}
我們用@Scheduled(fixedRate = 5000).
註釋sendMessage
方法@Scheduled(fixedRate = 5000).
這使sendMessage
每五秒鐘運行一次。然後,我們使用simpMessagingTemplate
實例將OutputMessage
發送到主題。 simpMessagingTemplate
和chuckNorris
實例從Spring上下文作為方法參數注入。
3.3。使用Flux::interval()
如果使用WebFlux,則可以使用[Flux::interval](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#interval-java.time.Duration-)
運算符。它將發布的無限流Long
通過選擇分隔的項D
uration
。
現在,讓我們在前面的示例中使用Flux。目標是每五秒鐘發送一次Chuck Norris
的報價。首先,我們需要實現InitializingBean
接口以在應用程序啟動時訂閱Flux
:
@Service
public class ReactiveScheduledPushMessages implements InitializingBean {
private SimpMessagingTemplate simpMessagingTemplate;
private ChuckNorris chuckNorris;
@Autowired
public ReactiveScheduledPushMessages(SimpMessagingTemplate simpMessagingTemplate, ChuckNorris chuckNorris) {
this.simpMessagingTemplate = simpMessagingTemplate;
this.chuckNorris = chuckNorris;
}
@Override
public void afterPropertiesSet() throws Exception {
Flux.interval(Duration.ofSeconds(5L))
// discard the incoming Long, replace it by an OutputMessage
.map((n) -> new OutputMessage("Chuck Norris (Flux::interval)",
chuckNorris.fact(),
new SimpleDateFormat("HH:mm").format(new Date())))
.subscribe(message -> simpMessagingTemplate.convertAndSend("/topic/pushmessages", message));
}
}
在這裡,我們使用構造函數注入來設置simpMessagingTemplate
和chuckNorris
實例。這次,調度邏輯在afterPropertiesSet(),
我們在實現InitializingBean
時將其重寫。服務啟動後,該方法將立即運行。
interval
運算符每五秒鐘發出一個Long
。然後, map
運算符會丟棄該值,並將其替換為我們的消息。最後,我們subscribe
Flux
來觸發每條消息的邏輯。
4。結論
在本教程中,我們已經看到實用程序類SimpMessagingTemplate
使通過WebSocket推送服務器消息變得容易。此外,我們已經看到了兩種調度一段代碼執行的方式。