1. 概要

この記事では、SpringFramework4.0で導入された新しいWebSocket機能を使用してメッセージングを実装する単純なWebアプリケーションを作成します。

WebSocketsは、Webブラウザーとサーバー間の双方向全二重持続的接続です。 WebSocket接続が確立されると、クライアントまたはサーバーがこの接続を閉じることを決定するまで、接続は開いたままになります。

典型的なユースケースは、チャットのように、アプリに複数のユーザーが相互に通信する場合です。 この例では、簡単なチャットクライアントを作成します。

2. Mavenの依存関係

これはMavenベースのプロジェクトであるため、最初に必要な依存関係をpom.xmlに追加します。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

さらに、 JSON を使用してメッセージの本文を作成するため、Jacksonの依存関係を追加する必要があります。 これにより、SpringはJavaオブジェクトをJSONとの間で変換できます。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.10.2</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId> 
    <version>2.10.2</version>
</dependency>

上記のライブラリの最新バージョンを入手したい場合は、 MavenCentralでそれらを探してください。

3. SpringでWebSocketを有効にする

最初に行うことは、WebSocket機能を有効にすることです。 これを行うには、アプリケーションに構成を追加し、このクラスに@EnableWebSocketMessageBrokerでアノテーションを付ける必要があります。

その名前が示すように、メッセージブローカーに支えられたWebSocketメッセージ処理を有効にします。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
         registry.addEndpoint("/chat");
         registry.addEndpoint("/chat").withSockJS();
    }
}

ここでは、メソッドconfigureMessageBrokerメッセージブローカーの構成に使用されていることがわかります。 まず、メモリ内のメッセージブローカが、「/topic」というプレフィックスが付いた宛先のクライアントにメッセージを戻すことができるようにします。

W eは、「/ app」プレフィックスを指定して、アプリケーションの注釈付きメソッドを対象とする宛先をフィルタリングすることにより、単純な構成を完了します( @MessageMapping を介して)。

registerStompEndpoints メソッドは、「/ chat」エンドポイントを登録し、SpringのSTOMPサポートを有効にします。 弾力性のために、SockJSなしで機能するエンドポイントもここに追加していることに注意してください。

このエンドポイントは、プレフィックスが「/ app」の場合、 ChatController.send()メソッドが処理するようにマップされるエンドポイントです。

また、 SockJSフォールバックオプションを有効にして、WebSocketが利用できない場合に代替メッセージングオプションを使用できるようにします。 WebSocketはまだすべてのブラウザーでサポートされているわけではなく、制限のあるネットワークプロキシによって除外される可能性があるため、これは便利です。

フォールバックにより、アプリケーションはWebSocket APIを使用できますが、実行時に必要な場合は、WebSocket以外の代替手段に正常に機能しなくなります。

4. メッセージモデルを作成する

プロジェクトをセットアップし、WebSocket機能を構成したので、送信するメッセージを作成する必要があります。

エンドポイントは、送信者名と本文がJSONオブジェクトであるSTOMPメッセージ内のテキストを含むメッセージを受け入れます。

メッセージは次のようになります。

{
    "from": "John",
    "text": "Hello!"
}

テキストを含むメッセージをモデル化するために、fromおよびtextプロパティを持つsimpleJavaオブジェクトを作成できます。

public class Message {

    private String from;
    private String text;

    // getters and setters
}

デフォルトでは、Springは Jackson ライブラリを使用して、モデルオブジェクトをJSONとの間で変換します。

5. メッセージ処理コントローラーを作成する

これまで見てきたように、STOMPメッセージングを操作するためのSpringのアプローチは、コントローラーメソッドを構成済みのエンドポイントに関連付けることです。 これは、@MessageMappingアノテーションによって可能になります。

エンドポイントとコントローラー間の関連付けにより、必要に応じてメッセージを処理できるようになります。

@MessageMapping("/chat")
@SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
    String time = new SimpleDateFormat("HH:mm").format(new Date());
    return new OutputMessage(message.getFrom(), message.getText(), time);
}

F またはこの例の目的では、構成された宛先に送信される出力メッセージを表すOutputMessageという名前の別のモデルオブジェクトを作成します。 オブジェクトに送信者と受信メッセージから取得したメッセージテキストを入力し、タイムスタンプで強化します。

メッセージを処理した後、@SendToアノテーションで定義された適切な宛先にメッセージを送信します。 「/topic /messages」宛先のすべてのサブスクライバーがメッセージを受信します。

6. ブラウザクライアントを作成する

サーバー側で構成を行った後、 sockjs-clientライブラリを使用して、メッセージングシステムと対話する単純なHTMLページを作成します。

まず、sockjsおよびstompJavascriptクライアントライブラリをインポートする必要があります。 次に、エンドポイントとの通信を開くための connect()関数、STOMPメッセージを送信するための sendMessage()関数、および disconnect()[X177X ]通信を閉じる機能:

<html>
    <head>
        <title>Chat WebSocket</title>
        <script src="resources/js/sockjs-0.3.4.js"></script>
        <script src="resources/js/stomp.js"></script>
        <script type="text/javascript">
            var stompClient = null;
            
            function setConnected(connected) {
                document.getElementById('connect').disabled = connected;
                document.getElementById('disconnect').disabled = !connected;
                document.getElementById('conversationDiv').style.visibility 
                  = connected ? 'visible' : 'hidden';
                document.getElementById('response').innerHTML = '';
            }
            
            function connect() {
                var socket = new SockJS('/chat');
                stompClient = Stomp.over(socket);  
                stompClient.connect({}, function(frame) {
                    setConnected(true);
                    console.log('Connected: ' + frame);
                    stompClient.subscribe('/topic/messages', function(messageOutput) {
                        showMessageOutput(JSON.parse(messageOutput.body));
                    });
                });
            }
            
            function disconnect() {
                if(stompClient != null) {
                    stompClient.disconnect();
                }
                setConnected(false);
                console.log("Disconnected");
            }
            
            function sendMessage() {
                var from = document.getElementById('from').value;
                var text = document.getElementById('text').value;
                stompClient.send("/app/chat", {}, 
                  JSON.stringify({'from':from, 'text':text}));
            }
            
            function showMessageOutput(messageOutput) {
                var response = document.getElementById('response');
                var p = document.createElement('p');
                p.style.wordWrap = 'break-word';
                p.appendChild(document.createTextNode(messageOutput.from + ": " 
                  + messageOutput.text + " (" + messageOutput.time + ")"));
                response.appendChild(p);
            }
        </script>
    </head>
    <body onload="disconnect()">
        <div>
            <div>
                <input type="text" id="from" placeholder="Choose a nickname"/>
            </div>
            <br />
            <div>
                <button id="connect" onclick="connect();">Connect</button>
                <button id="disconnect" disabled="disabled" onclick="disconnect();">
                    Disconnect
                </button>
            </div>
            <br />
            <div id="conversationDiv">
                <input type="text" id="text" placeholder="Write a message..."/>
                <button id="sendMessage" onclick="sendMessage();">Send</button>
                <p id="response"></p>
            </div>
        </div>

    </body>
</html>

7. 例のテスト

この例をテストするために、いくつかのブラウザウィンドウを開いて、次のチャットページにアクセスできます。

http://localhost:8080

これが完了したら、ニックネームを入力して接続ボタンを押すことでチャットに参加できます。 メッセージを作成して送信すると、チャットに参加しているすべてのブラウザセッションでメッセージを確認できます。

スクリーンショットを見て例を確認してください。

8. 結論

このチュートリアルでは、SpringのWebSocketサポートについて説明しました。 サーバー側の構成を確認し、sockjsおよびstomp Javascriptライブラリを使用して、単純なクライアント側のカウンターパートを構築しました。

サンプルコードはGitHubプロジェクトにあります。