39
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Spring 4.3 WebSocket関連の主な変更点(+簡易アプリ作成!!)

Last updated at Posted at 2016-05-30

今回は、Spring Framework 4.3の変更点紹介シリーズの第6回で、WebSocket関連の変更点を紹介します。

シリーズ

動作検証環境

  • Spring Framework 4.3.0.RELEASE
  • Spring Boot 1.4.0.BUILD-SNAPSHOT (2016/6/11時点)

WebSocket Messaging Improvements

今回は、WebSocket関連の主な変更点をみていきます。公式リファレンスを見る限りでは、機能追加はないようです。

No WebSocket関連の主な変更点
1 @SendTo@SendToUserをクラスレベルに付与することができるようになります。

まず、WebSocketアプリを作ってみようぜ :thumbsup:

WebSocket関連の変更点は少ないので、WebSocketを使った簡単なアプリを作成した後に、今回の変更点を紹介したいと思います。題材は、Spring Guidesで紹介されている「Using WebSocket to build an interactive web application」をベースに少しだけアレンジを加えています。

開発プロジェクトを作成する

SPRING INITIALIZRにて、Dependenciesに「WebSocket」を選択してプロジェクトをダウンロードしてください。ダウンロードしたZipファイルに任意のディレクトリに解凍し、pom.xmlspring-boot-starter-websocketが依存ライブラリに追加されていることを確認します。追加されていない場合は追加してください。

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

モデルを作る

クライアント >>> サーバー間のモデル

形式
{"name":"xxxx"}
package com.example.websocket;

public class Message {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

サーバー >>> クライアント間のモデル

形式
{"content":"Hello, xxxx!"}
package com.example.websocket;

public class Greeting {
    private String content;
    public Greeting(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    }
}

@Controllerを作る

package com.example.websocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import java.util.concurrent.TimeUnit;

@Controller
public class GreetingController {

    @MessageMapping("/hello") // エンドポイントの指定
    @SendTo("/topic/greetings") // メッセージの宛先を指定
    public Greeting greeting(Message message) {
        TimeUnit.SECONDS.sleep(3);
        return new Greeting("Hello, " + message.getName() + "!");
    }

}

WebSocket機能のコンフィギュレーションを行う

package com.example.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@EnableWebSocketMessageBroker // WebSocketのメッセージブローカーのBean定義を有効化する
@Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { // AbstractWebSocketMessageBrokerConfigurerを継承しWebSocket関連のBean定義をカスタマイズする

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/hello").withSockJS(); // WebSocketのエンドポイント (接続時に指定するエンドポイント)を指定
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app"); // アプリケーション(Controller)でハンドリングするエンドポイントのプレフィックス
        registry.enableSimpleBroker("/topic", "/queue"); // Topic(Pub-Sub)とQueue(P2P)を有効化 >>> メッセージブローカーがハンドリングする
    }

}

ざっくり図で表すと以下のようなイメージになります。

spr43-websocket-stomp.png

クライアント(HTML)を作る

src/main/resources/static/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <script src="/webjars/sockjs-client/1.0.2/sockjs.js"></script>
    <script src="/webjars/stomp-websocket/2.3.3/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('/hello'); // WebSocketに接続
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function (frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/topic/greetings', function (greeting) { // 挨拶の応答(/topic/greetings)を購読する
                    showGreeting(JSON.parse(greeting.body).content);
                });
            });
        }

        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
            }
            setConnected(false);
            console.log("Disconnected");
        }

        function sendName() {
            var name = document.getElementById('name').value;
            stompClient.send("/app/hello", {}, JSON.stringify({'name': name})); // メッセージを送信
        }

        function showGreeting(message) {
            var response = document.getElementById('response');
            var p = document.createElement('p');
            p.style.wordWrap = 'break-word';
            p.appendChild(document.createTextNode(message));
            response.appendChild(p);
        }
    </script>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div>
    <div>
        <button id="connect" onclick="connect();">Connect</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
    </div>
    <div id="conversationDiv">
        <label>What is your name?</label><input type="text" id="name"/>
        <button id="sendName" onclick="sendName();">Send</button>
        <p id="response"></p>
    </div>
</div>
</body>
</html>

sockjsとstompのjsをインストールする(webjarを利用)。

pom.xml
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>sockjs-client</artifactId>
	<version>1.0.2</version>
</dependency>

<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>stomp-websocket</artifactId>
	<version>2.3.3</version>
</dependency>

Spring Bootを起動する

$ ./mvnw spring-boot:run

ブラウザでアクセスする

ブラウザ経由で http://localhost:8080/ にアクセスします。

spr43-websocket-index.png

「Connect」ボタンを押下して、WebSocketに接続します。

spr43-websocket-connect.png

名前を入力し「Send」ボタンを押下して、WebSocket経由でController(/app/hello)を呼び出します。約3秒後に「 Hello, 名前!」が表示されます。

spr43-websocket-send.png

例外ハンドリングを実装してみよう

Controller内でエラーが発生したら、送信者だけにエラーメッセージを通知してみましょう。

形式
{"message":"System error occurred."}
package com.example.websocket;

public class MessagingError {
    private String message;
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
@Controller
public class GreetingController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(Message message) throws Exception {
        if ("error".equals(message.getName())) { // 暫定的に例外を発生させるコードを埋め込む
            throw new NullPointerException("System error occurred.");
        }
        TimeUnit.SECONDS.sleep(3);
        return new Greeting("Hello, " + message.getName() + "!");
    }

    // 例外ハンドリング用のメソッドを追加する
    @MessageExceptionHandler
    @SendToUser("/queue/errors") // 送信者のみを宛先にする
    public MessagingError handleException(Throwable exception) {
        MessagingError error = new MessagingError();
        error.setMessage(exception.getMessage());
        return error;
    }

}
src/main/resources/static/index.html
function connect() {
    var socket = new SockJS('/hello');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
        stompClient.subscribe('/user/queue/errors', function (error) { // エラー通知用のQueue(/user/queue/errors)を購読
            alert(JSON.parse(error.body).message);
        });
    });
}

名前に「error」を入力して「Send」ボタンを押下すると、警告ダイアログが表示されます。

spr43-websocket-error.png

細かい説明は省きましたが、こんな感じでWebSokcetが利用できます!! 簡単ですね :smile:
ではでは、ここからはSpring 4.3の変更点を紹介していきます。

@SendTo@SendToUserをクラスレベルに付与することができる :thumbsup:

Spring 4.3から、@SendTo@SendToUserをクラスレベルに付与することで、応答メッセージの宛先を各メソッドで共有することができます。

@SendTo

@SendTo("/topic/greetings") // クラスレベルに指定して、応答先を各メソッドで共有!!
@Controller
public class GreetingController {
    @MessageMapping("/hello")
    public Greeting greeting(Message message) throws Exception {
        TimeUnit.SECONDS.sleep(3);
        return new Greeting("Hello, " + message.getName() + "!");
    }
    // ...
}

@SendToUser

@SendToUserは送信者のみにメッセージを送信することができるため、エラー応答時に使われることが多い。ここでは、共通の例外ハンドラーを作成し、エラー通知の宛先を各メソッドで共有しています。

package com.example.websocket;

import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.web.bind.annotation.ControllerAdvice;

@SendToUser("/queue/errors") // クラスレベルに指定して、応答先を各メソッドで共有!!
@ControllerAdvice
public class MessagingGlobalExceptionHandler {

    @MessageExceptionHandler
    public MessagingError handleException(Throwable exception) {
        MessagingError error = new MessagingError();
        error.setMessage(exception.getMessage());
        return error;
    }

    // ...

}

まとめ

今回は、WebSocket関連の主な変更点を紹介しましたが、大きな変更はないようですね。次回は最終回で、「テスト関連の主な変更点」を紹介する予定です。

参考サイト

39
38
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?