1. 概要

このチュートリアルでは、 WebSockets を使用して、サーバーからブラウザーにスケジュールされたメッセージを送信する方法を説明します。 別の方法として、サーバー送信イベント(SSE)を使用することもできますが、この記事ではこれについては説明しません。

Springは、さまざまなスケジューリングオプションを提供します。 まず、@Scheduledアノテーションについて説明します。 次に、ProjectReactorが提供するFlux::よいメソッドの例を示します。 このライブラリは、 Webflux アプリケーションですぐに使用でき、任意のJavaプロジェクトでスタンドアロンライブラリとして使用できます。

また、 Quarterzスケジューラーのようなより高度なメカニズムも存在しますが、それらについては説明しません。

2. シンプルなチャットアプリケーション

前の記事では、WebSocketを使用してチャットアプリケーションを構築しました。 チャットボットという新機能で拡張しましょう。 これらのボットは、スケジュールされたメッセージをブラウザにプッシュするサーバー側のコンポーネントです。

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 ライブラリを使用して、ボットのメッセージを生成します。 このライブラリは、テストデータを生成するためによく使用されます。 ここでは、「 ChuckNorris」という名前のゲストをチャットルームに追加します。

コードを見てみましょう:

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. サーバ側

前回の記事で、SpringWebSocketを構成する方法を見てきました。 その構成を少し変更してみましょう。

@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コンテキストで@Beanとして使用可能になっています。 AbstractMessageBrokerConfiguration がクラスパスにある場合、自動構成によってどのように宣言されているかを確認できます。 したがって、任意のSpringコンポーネントに注入できます。

その後、これを使用してトピック / topic /pushmessagesにメッセージを公開します。 このクラスでは、simpMessagingTemplateという名前の変数にBeanが挿入されていると想定しています。

simpMessagingTemplate.convertAndSend("/topic/pushmessages", 
  new OutputMessage("Chuck Norris", faker.chuckNorris().fact(), time));

クライアント側の例で前に示したように、クライアントはそのトピックにサブスクライブして、メッセージが到着したときにメッセージを処理します。

3. プッシュメッセージのスケジュール

Springエコシステムでは、さまざまなスケジューリング方法から選択できます。 Spring MVCを使用する場合、 @Scheduled アノテーションは、その単純さのために自然な選択として提供されます。 Spring Webfluxを使用する場合は、ProjectReactorのFlux::intervalメソッドを使用することもできます。 それぞれの例を1つ見ていきます。

3.1. 構成

チャットボットは、JavaFakerのチャックノリスジェネレーターを使用します。 必要な場所に注入できるように、Beanとして構成します。

@Configuration
class AppConfig {

    @Bean
    public ChuckNorris chuckNorris() {
        return (new Faker()).chuckNorris();
    }
}

3.2. @Scheduledを使用

ボットの例はスケジュールされたメソッドです。 実行すると、SimpleMessagingTemplateを使用してWebSocketを介してOutputMessagePOJOを送信します。

その名前が示すように、 @Scheduledアノテーションにより、メソッドを繰り返し実行できます。 これにより、単純なレートベースのスケジューリングまたはより複雑な「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)。 これによりメッセージを送る 5秒ごとに実行します。 次に、 simpMessagingTemplate インスタンスを使用して、OutputMessageをトピックに送信します。 simpMessagingTemplateおよびchuckNorrisインスタンスは、メソッドパラメーターとしてSpringコンテキストから注入されます。

3.3. Flux ::interval()を使用する

WebFluxを使用する場合は、Flux::interval演算子を使用できます。選択したDurationで区切られたLongアイテムの無限ストリームを公開します。

それでは、前の例でFluxを使用してみましょう。 目標は、チャックノリスから5秒ごとに見積もりを送信することです。 まず、アプリケーションの起動 Flux をサブスクライブするために、InitializingBeanインターフェイスを実装する必要があります。

@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 オペレーターは、5秒ごとにLongを発行します。 次に、 map オペレーターはその値を破棄し、メッセージに置き換えます。 最後に、サブスクライブ Flux にサブスクライブして、各メッセージのロジックをトリガーします。

4. 結論

このチュートリアルでは、ユーティリティクラス SimpmessagingTemplate を使用すると、WebSocketを介してサーバーメッセージを簡単にプッシュできることを確認しました。 さらに、コードの実行をスケジュールする2つの方法を見てきました。

いつものように、例のソースコードはGitHubから入手できます。