このガイドでは、レスポンスに Cross-Origin Resource Sharing(CORS)のヘッダーを含む Spring で「Hello, World」RESTful Web サービスを作成するプロセスを説明します。Spring CORS サポートの詳細については、このブログ投稿を参照してください (英語)

構築するもの

次のリストに示すように、http://localhost:8080/greeting で HTTP GET リクエストを受け入れ、グリーティングの JSON 表現で応答するサービスを構築します。

{"id":1,"content":"Hello, World!"}

次のリストに示すように、クエリ文字列のオプションの name パラメーターを使用して、グリーティングをカスタマイズできます。

http://localhost:8080/greeting?name=User

name パラメーター値は、次のリストに示すように、World のデフォルト値をオーバーライドし、レスポンスに反映されます。

{"id":1,"content":"Hello, User!"}

このサービスは、Spring Framework CORS サポートを使用して関連する CORS レスポンスヘッダーを追加するという点で、RESTful Web サービスの構築で説明されているサービスとは少し異なります。

必要なもの

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

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

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

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

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

Spring Initializr から開始

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

次のリストは、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>rest-service-cors</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>rest-service-cors</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-web</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-web'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

httpclient 依存関係の追加

テスト(complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java)では、Apache httpclient ライブラリが必要です。

Apache httpclient ライブラリを Maven に追加するには、次の依存関係を追加します。

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <scope>test</scope>
</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>rest-service-cors</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>rest-service-cors</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-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<scope>test</scope>
		</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>

Apache httpclient ライブラリを Gradle に追加するには、次の依存関係を追加します。

testImplementation 'org.apache.httpcomponents:httpclient'

次のリストは、完成した 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-web'
	testImplementation 'org.apache.httpcomponents:httpclient'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

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

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

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

サービスは、/greeting への GET リクエストを処理します。オプションで、クエリ文字列に name パラメーターを使用します。GET リクエストは、あいさつを表すために本文に JSON を含む 200 OK レスポンスを返す必要があります。次のリストのようになります。

{
    "id": 1,
    "content": "Hello, World!"
}

id フィールドは挨拶の一意の識別子であり、content は挨拶のテキスト表現です。

挨拶表現をモデル化するには、リソース表現クラスを作成します。次のリスト(src/main/java/com/example/restservicecors/Greeting.java から)が示すように、id および content データのフィールド、コンストラクター、およびアクセサーを持つプレーンな古い Java オブジェクトを提供します。

package com.example.restservicecors;

public class Greeting {

	private final long id;
	private final String content;

	public Greeting() {
		this.id = -1;
		this.content = "";
	}

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

	public long getId() {
		return id;
	}

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

リソースコントローラーを作成する

RESTful Web サービスを構築する Spring のアプローチでは、HTTP リクエストはコントローラーによって処理されます。これらのコンポーネントは @Controller(Javadoc) アノテーションによって簡単に識別され、次のリスト(src/main/java/com/example/restservicecors/GreetingController.java から)に示されている GreetingController は、Greeting クラスの新しいインスタンスを返すことにより、/greeting に対する GET リクエストを処理します。

package com.example.restservicecors;

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

	private static final String template = "Hello, %s!";

	private final AtomicLong counter = new AtomicLong();
	@CrossOrigin(origins = "http://localhost:8080")
	@GetMapping("/greeting")
	public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== get greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));
	}

}

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

@RequestMapping アノテーションは、/greeting への HTTP リクエストが greeting() メソッドにマップされることを保証します。

前の例では、@RequestMapping(method = RequestMethod.GET) のショートカットとして機能する @GetMapping アノテーションを使用しています。この場合、テストに便利な GET を使用します。Spring は、オリジンが CORS 構成と一致しない場合でも GET リクエストを拒否します。ブラウザは CORS プリフライトリクエストを送信する必要はありませんが、プリフライトチェックをトリガーする場合は、@PostMapping を使用して、本文で JSON を受け入れることができます。

@RequestParam は、name クエリ文字列パラメーターの値を greeting() メソッドの name パラメーターにバインドします。このクエリ文字列パラメーターは required ではありません。リクエストにない場合、World の defaultValue が使用されます。

メソッド本体の実装は、counter の次の値に基づく id 属性の値と、クエリパラメーターまたはデフォルト値に基づく content の値を使用して、新しい Greeting オブジェクトを作成して返します。また、グリーティング template を使用して、指定された name をフォーマットします。

従来の MVC コントローラーと前述の RESTful Web サービスコントローラーの主な違いは、HTTP レスポンスの本文が作成される方法です。この RESTful Web サービスコントローラーは、ビューテクノロジーに依存して、グリーティングデータを HTML にサーバー側でレンダリングするのではなく、Greeting オブジェクトを生成して返します。オブジェクトデータは、JSON として HTTP レスポンスに直接書き込まれます。

これを実現するために、greeting() メソッドの @ResponseBody(Javadoc) アノテーションは、Spring MVC に、サーバー側のビューレイヤーを介してグリーティングオブジェクトをレンダリングする必要がないことを伝えます。代わりに、返されるグリーティングオブジェクトはレスポンス本文であり、直接書き出す必要があります。

Greeting オブジェクトは JSON に変換する必要があります。Spring の HTTP メッセージコンバーターのサポートにより、この変換を手動で行う必要はありません。Jackson (英語) はクラスパス上にあるため、Spring の MappingJackson2HttpMessageConverter(Javadoc) が自動的に選択され、Greeting インスタンスが JSON に変換されます。

CORS を有効にする

個々のコントローラーまたはグローバルで、クロスオリジンリソース共有(CORS)を有効にできます。次のトピックでは、その方法について説明します。

コントローラーメソッド CORS 設定

RESTful Web サービスのレスポンスに CORS アクセス制御ヘッダーが含まれるように、次のリスト(src/main/java/com/example/restservicecors/GreetingController.java から)が示すように、ハンドラーメソッドに @CrossOrigin アノテーションを追加する必要があります。

	@CrossOrigin(origins = "http://localhost:8080")
	@GetMapping("/greeting")
	public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== get greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));

この @CrossOrigin アノテーションは、この特定のメソッドでのみクロスオリジンリソース共有を有効にします。デフォルトでは、@RequestMapping アノテーションで指定されたすべてのオリジン、すべてのヘッダー、および HTTP メソッドを許可します。また、30 分の maxAge が使用されます。次のいずれかのアノテーション属性の値を指定することにより、この動作をカスタマイズできます。

  • origins

  • methods

  • allowedHeaders

  • exposedHeaders

  • allowCredentials

  • maxAge.

この例では、http://localhost:8080 のみがクロスオリジンリクエストの送信を許可しています。

また、コントローラークラスレベルで @CrossOrigin アノテーションを追加して、このクラスのすべてのハンドラーメソッドで CORS を有効にすることもできます。

グローバル CORS 設定

詳細なアノテーションベースの構成に加えて(または代替として)、いくつかのグローバル CORS 構成も定義できます。これは Filter の使用に似ていますが、Spring MVC 内で宣言し、きめの細かい @CrossOrigin 構成と組み合わせることができます。デフォルトでは、すべてのオリジンと GETHEAD、および POST メソッドが許可されています。

次のリスト(src/main/java/com/example/restservicecors/GreetingController.java から)は、GreetingController クラスの greetingWithJavaconfig メソッドを示しています。

	@GetMapping("/greeting-javaconfig")
	public Greeting greetingWithJavaconfig(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== in greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));
greetingWithJavaconfig メソッドと greeting メソッド(コントローラーレベルの CORS 構成で使用)の違いは、ルート(/greeting ではなく /greeting-javaconfig)と @CrossOrigin オリジンの存在です。

次のリスト(src/main/java/com/example/restservicecors/RestServiceCorsApplication.java から)は、アプリケーションクラスに CORS マッピングを追加する方法を示しています。

	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
			}
		};
	}

プロパティ(例の allowedOrigins など)を簡単に変更したり、この CORS 構成を特定のパスパターンに適用したりできます。

グローバルレベルとコントローラーレベルの CORS 構成を組み合わせることができます。

アプリケーションクラスの作成

Spring Initializr は、必要最低限のアプリケーションクラスを作成します。次のリスト(initial/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java から)は、その初期クラスを示しています。

package com.example.restservicecors;

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

@SpringBootApplication
public class RestServiceCorsApplication {

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

}

クロスオリジンリソース共有の処理方法を構成するメソッドを追加する必要があります。次のリスト(complete/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java から)は、その方法を示しています。

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
			}
		};
	}

次のリストは、完成したアプリケーションクラスを示しています。

package com.example.restservicecors;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class RestServiceCorsApplication {

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

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
			}
		};
	}

}

@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-rest-service-cors-0.1.0.jar

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

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

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

サービスをテストする

サービスが開始されたため、ブラウザで http://localhost:8080/greeting にアクセスすると、次のように表示されます。

{"id":1,"content":"Hello, World!"}

http://localhost:8080/greeting?name=User にアクセスして、name クエリ文字列パラメーターを提供します。次のリストに示すように、content 属性の値は Hello, World! から Hello User! に変わります。

{"id":2,"content":"Hello, User!"}

この変更は、GreetingController の @RequestParam 配置が期待どおりに機能することを示しています。name パラメーターには World のデフォルト値が指定されていますが、照会ストリングを介して常に明示的にオーバーライドできます。

また、id 属性が 1 から 2 に変更されました。これは、複数のリクエストにわたって同じ GreetingController インスタンスに対して作業していること、およびその counter フィールドが予想どおりに各呼び出しで増加していることを証明しています。

これで、CORS ヘッダーが適切に配置されていることをテストし、別のオリジンの Javascript クライアントがサービスにアクセスできるようになります。そのためには、サービスを使用する Javascript クライアントを作成する必要があります。次のリストは、このようなクライアントを示しています。

最初に、次の内容の hello.js (complete/public/hello.js から)という名前の単純な Javascript ファイルを作成します。

$(document).ready(function() {
    $.ajax({
        url: "http://localhost:8080/greeting"
    }).then(function(data, status, jqxhr) {
       $('.greeting-id').append(data.id);
       $('.greeting-content').append(data.content);
       console.log(jqxhr);
    });
});

このスクリプトは jQuery を使用して http://localhost:8080/greeting で REST サービスを使用します。次のリスト(complete/public/index.html から)が示すように、index.html によってロードされます。

<!DOCTYPE html>
<html>
    <head>
        <title>Hello CORS</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script src="hello.js"></script>
    </head>

    <body>
        <div>
            <p class="greeting-id">The ID is </p>
            <p class="greeting-content">The content is </p>
        </div>
    </body>
</html>
これは、本質的に jQuery で RESTful Web サービスの使用で作成された REST クライアントであり、ポート 8080 のローカルホストで実行されるときにサービスを消費するようにわずかに変更されています。このクライアントの開発方法の詳細については、そのガイドを参照してください。

ローカルホストで実行されているクライアントをポート 8080 で起動するには、次の Maven コマンドを実行します。

./mvnw spring-boot:run

Gradle を使用する場合は、次のコマンドを使用できます。

./gradlew bootRun

アプリが起動したら、ブラウザで http://localhost:8080 を開きます。ここで、次のように表示されます。

The browser will fail the request if the CORS headers are missing from the response. No data will be rendered into the DOM.

CORS の動作をテストするには、別のサーバーまたはポートからクライアントを起動する必要があります。そうすることで、2 つのアプリケーション間の衝突を回避するだけでなく、クライアントコードがサービスとは異なるオリジンから提供されるようになります。localhost でポート 9000(およびポート 8080 ですでに実行されているアプリ)で実行されているアプリを起動するには、次の Maven コマンドを実行します。

./mvnw spring-boot:run -Dserver.port=9000

Gradle を使用する場合は、次のコマンドを使用できます。

./gradlew bootRun --args="--server.port=9000"

アプリが起動したら、ブラウザで http://localhost:9000 を開きます。ここで、次のように表示されます。

Model data retrieved from the REST service is rendered into the DOM if the proper CORS headers are in the response.

サービスレスポンスに CORS ヘッダーが含まれている場合、ID とコンテンツがページにレンダリングされます。ただし、CORS ヘッダーが欠落している(またはクライアントにとって不十分である)場合、ブラウザーはリクエストに失敗し、値は DOM にレンダリングされません。

要約

おめでとう! Spring とのクロスオリジンリソース共有を含む RESTful Web サービスを開発しました。