このガイドでは、ブラウザとサーバー間でメッセージを送受信する「Hello、world」アプリケーションを作成するプロセスを順を追って説明します。WebSocket は、TCP の上にある薄くて軽いレイヤーです。これにより、「サブプロトコル」を使用してメッセージを埋め込むのに適しています。このガイドでは、Spring で STOMP (英語) メッセージングを使用して、インタラクティブな Web アプリケーションを作成します。

構築するもの

ユーザーの名前を伝えるメッセージを受け入れるサーバーを構築します。レスポンスとして、サーバーはクライアントがサブスクライブしているキューにグリーティングをプッシュします。

必要なもの

このガイドを完了する方法

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-messaging-stomp-websocket/complete のコードに対して結果を確認できます。

Spring Initializr から開始

すべての Spring アプリケーションでは、Spring Initializr (英語) から始める必要があります。Initializr は、アプリケーションに必要なすべての依存関係をすばやく取り込む方法を提供し、多くの設定を行います。この例では、Websocket 依存関係のみが必要です。

次のリストは、Maven を選択したときに作成される pom.xml ファイルを示しています。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>messaging-stomp-websocket</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>messaging-stomp-websocket</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

次のリストは、Gradle を選択したときに作成される build.gradle ファイルを示しています。

plugins {
	id 'org.springframework.boot' version '2.3.2.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

依存関係の追加

Spring Initializr は、この場合に必要なすべてを提供するわけではありません。Maven の場合、次の依存関係を追加する必要があります。

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>webjars-locator-core</artifactId>
</dependency>
<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>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>bootstrap</artifactId>
  <version>3.3.7</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>jquery</artifactId>
  <version>3.1.1-1</version>
</dependency>

次のリストは、完成した pom.xml ファイルを示しています。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>messaging-stomp-websocket</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>messaging-stomp-websocket</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Gradle を使用する場合、次の依存関係を追加する必要があります。

implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
implementation 'org.webjars:bootstrap:3.3.7'
implementation 'org.webjars:jquery:3.1.1-1'

次のリストは、完成した build.gradle ファイルを示しています。

plugins {
	id 'org.springframework.boot' version '2.3.2.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

リソース表現クラスを作成する

プロジェクトとビルドシステムのセットアップが完了したため、STOMP メッセージサービスを作成できます。

サービスの相互作用について考えることでプロセスを開始します。

サービスは、本文が JSON オブジェクトである STOMP メッセージに名前を含むメッセージを受け入れます。名前が Fred の場合、メッセージは次のようになります。

{
    "name": "Fred"
}

名前を伝えるメッセージをモデル化するために、次のリスト(src/main/java/com/example/messagingstompwebsocket/HelloMessage.java から)が示すように、name プロパティと対応する getName() メソッドを持つプレーンな古い Java オブジェクトを作成できます。

package com.example.messagingstompwebsocket;

public class HelloMessage {

  private String name;

  public HelloMessage() {
  }

  public HelloMessage(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

メッセージを受信して名前を抽出すると、サービスはグリーティングを作成し、クライアントがサブスクライブしている別のキューにそのグリーティングを公開することで処理します。グリーティングは JSON オブジェクトでもあり、次のリストに示すように:

{
    "content": "Hello, Fred!"
}

挨拶表現をモデル化するには、次のリスト(src/main/java/com/example/messagingstompwebsocket/Greeting.java から)に示すように、content プロパティと対応する getContent() メソッドを持つ別のプレーンな古い Java オブジェクトを追加します。

package com.example.messagingstompwebsocket;

public class Greeting {

  private String content;

  public Greeting() {
  }

  public Greeting(String content) {
    this.content = content;
  }

  public String getContent() {
    return content;
  }

}

Spring は、Jackson JSON (英語) ライブラリを使用して、タイプ Greeting のインスタンスを JSON に自動的にマーシャリングします。

次に、hello メッセージを受信して挨拶メッセージを送信するコントローラーを作成します。

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

STOMP メッセージングを操作する Spring のアプローチでは、STOMP メッセージを @Controller(Javadoc) クラスにルーティングできます。例: GreetingController (src/main/java/com/example/messagingstompwebsocket/GreetingController.java から)は、次のリストに示すように、/hello 宛先へのメッセージを処理するためにマップされます。

package com.example.messagingstompwebsocket;

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

@Controller
public class GreetingController {


  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
  }

}

このコントローラーは簡潔でシンプルですが、多くのことが行われています。段階的にそれを分解します。

@MessageMapping(Javadoc) アノテーションは、メッセージが /hello 宛先に送信された場合、greeting() メソッドが呼び出されることを保証します。

メッセージのペイロードは HelloMessage オブジェクトにバインドされ、greeting() に渡されます。

内部的に、メソッドの実装は、スレッドを 1 秒間スリープさせることにより、処理の遅延をシミュレートします。これは、クライアントがメッセージを送信した後、サーバーが非同期にメッセージを処理する必要がある限り、処理できることを示すためです。クライアントは、レスポンスを待たずに必要な作業を続行できます。

1 秒の遅延の後、greeting() メソッドは Greeting オブジェクトを作成して返します。戻り値は、@SendTo(Javadoc) アノテーションで指定されている /topic/greetings のすべてのサブスクライバーにブロードキャストされます。入力メッセージからの名前はサニタイズされることに注意してください。この場合、クライアント側のブラウザ DOM でエコーバックされて再レンダリングされるためです。

STOMP メッセージング用に Spring を構成する

サービスの必須コンポーネントが作成されたため、Spring を構成して WebSocket および STOMP メッセージングを有効にすることができます。

次のリストに似た WebSocketConfig という名前の Java クラスを作成します(src/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java から)。

package com.example.messagingstompwebsocket;

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

@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("/gs-guide-websocket").withSockJS();
  }

}

WebSocketConfig には @Configuration のアノテーションが付けられ、Spring 構成クラスであることを示します。また、@EnableWebSocketMessageBroker(Javadoc) でアノテーションが付けられています。名前が示すように、@EnableWebSocketMessageBroker は WebSocket メッセージ処理を有効にし、メッセージブローカーによって支援されます。

configureMessageBroker() メソッドは、WebSocketMessageBrokerConfigurer のデフォルトメソッドを実装して、メッセージブローカーを構成します。まず、enableSimpleBroker() を呼び出して、単純なメモリベースのメッセージブローカーが、/topic をプレフィックスとして持つ宛先でクライアントにグリーティングメッセージを戻すことができるようにします。また、@MessageMapping アノテーションが付けられたメソッドにバインドされているメッセージの /app プレフィックスも指定します。このプレフィックスは、すべてのメッセージマッピングを定義するために使用されます。例: /app/hello は、GreetingController.greeting() メソッドが処理するためにマップされるエンドポイントです。

registerStompEndpoints() メソッドは /gs-guide-websocket エンドポイントを登録し、WebSocket が使用できない場合に代替トランスポートを使用できるように SockJS フォールバックオプションを有効にします。SockJS クライアントは、/gs-guide-websocket への接続を試み、利用可能な最適なトランスポート(websocket、xhr-streaming、xhr-polling など)の使用を試みます。

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

サーバー側のピースを配置すると、サーバー側とメッセージを送受信する JavaScript クライアントに注意を向けることができます。

次のリストに似た index.html ファイルを作成します(src/main/resources/static/index.html から)。

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<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 id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

この HTML ファイルは、SockJS および STOMP JavaScript ライブラリをインポートします。これらのライブラリは、Websocket を介した STOMP を介してサーバーと通信するために使用されます。クライアントアプリケーションのロジックを含む app.js もインポートします。次のリスト(src/main/resources/static/app.js から)は、そのファイルを示しています。

var stompClient = null;

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    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);
        });
    });
}

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

function sendName() {
    stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });
});

この JavaScript ファイルの主要な部分は、connect() および sendName() 関数です。

connect() 関数は、SockJS (GitHub) stomp.js (英語) を使用して、SockJS サーバーが接続を待機する /gs-guide-websocket への接続を開きます。接続が成功すると、クライアントは /topic/greetings 宛先にサブスクライブし、サーバーはグリーティングメッセージを発行します。その宛先でグリーティングが受信されると、DOM に段落要素が追加され、グリーティングメッセージが表示されます。

sendName() 関数は、ユーザーが入力した名前を取得し、STOMP クライアントを使用して /app/hello 宛先(GreetingController.greeting() が受信する)に送信します。

アプリケーションを実行可能にする

Spring Boot は、アプリケーションクラスを作成します。この場合、さらに変更する必要はありません。これを使用して、このアプリケーションを実行できます。次のリスト(src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java から)は、アプリケーションクラスを示しています。

package com.example.messagingstompwebsocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MessagingStompWebsocketApplication {

  public static void main(String[] args) {
    SpringApplication.run(MessagingStompWebsocketApplication.class, args);
  }
}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、およびさまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、およびサービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、およびリソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、およびデプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-messaging-stomp-websocket-0.1.0.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-messaging-stomp-websocket-0.1.0.jar
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。

ロギング出力が表示されます。サービスは数秒以内に起動して実行されるはずです。

サービスをテストする

サービスが実行されたため、ブラウザで http://localhost:8080 をポイントし、接続ボタンをクリックします。

接続を開くと、名前を尋ねられます。名前を入力して送信をクリックします。あなたの名前は、STOMP 経由で JSON メッセージとしてサーバーに送信されます。1 秒のシミュレートされた遅延の後、サーバーはページに表示される「Hello」グリーティングと共にメッセージを送り返します。この時点で、別の名前を送信するか、切断ボタンをクリックして接続を閉じることができます。

要約

おめでとう! Spring を使用して STOMP ベースのメッセージングサービスを開発しました。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください (GitHub)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives クリエイティブコモンズライセンス (英語) でリリースされています。