リクエストマッピング

このセクションでは、アノテーション付きコントローラーのリクエストマッピングについて説明します。

@RequestMapping

@RequestMapping アノテーションは、リクエストをコントローラーメソッドにマップするために使用されます。URL、HTTP メソッド、リクエストパラメーター、ヘッダー、メディア型で一致するさまざまな属性があります。クラスレベルで使用して共有マッピングを表現したり、メソッドレベルで使用して特定のエンドポイントマッピングに絞り込んだりできます。

@RequestMapping の HTTP メソッド固有のショートカットバリアントもあります。

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

ほとんどのコントローラーメソッドは、デフォルトですべての HTTP メソッドに一致する @RequestMapping を使用するのではなく、特定の HTTP メソッドにマップされる必要があるため、上記のアノテーションはカスタムアノテーションです。同時に、共有マッピングを表現するには、クラスレベルで @RequestMapping が必要です。

@RequestMapping は、同じ要素 (クラス、インターフェース、メソッド) で宣言されている他の @RequestMapping アノテーションと組み合わせて使用することはできません。同じ要素で複数の @RequestMapping アノテーションが検出された場合、警告がログに記録され、最初のマッピングのみが使用されます。これは、@GetMapping@PostMapping などの合成 @RequestMapping アノテーションにも当てはまります。

次の例では、型およびメソッドレベルのマッピングを使用しています。

  • Java

  • Kotlin

@RestController
@RequestMapping("/persons")
class PersonController {

	@GetMapping("/{id}")
	public Person getPerson(@PathVariable Long id) {
		// ...
	}

	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	public void add(@RequestBody Person person) {
		// ...
	}
}
@RestController
@RequestMapping("/persons")
class PersonController {

	@GetMapping("/{id}")
	fun getPerson(@PathVariable id: Long): Person {
		// ...
	}

	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	fun add(@RequestBody person: Person) {
		// ...
	}
}

URI パターン

glob パターンとワイルドカードを使用してリクエストをマッピングできます:

パターン 説明 サンプル

?

1 文字に一致

"/pages/t?st.html" matches "/pages/test.html" and "/pages/t3st.html"

*

パスセグメント内の 0 個以上の文字に一致します

"/resources/*.png" matches "/resources/file.png"

"/projects/*/versions" matches "/projects/spring/versions" but does not match "/projects/spring/boot/versions"

**

パスの終わりまで 0 個以上のパスセグメントに一致します

"/resources/**" matches "/resources/file.png" and "/resources/images/file.png"

"/resources/**/file.png" is invalid as ** is only allowed at the end of the path.

{name}

パスセグメントを照合し、"name" という名前の変数としてキャプチャーします

"/projects/{project}/versions" matches "/projects/spring/versions" and captures project=spring

{name:[a-z]}+

正規表現 "[a-z]"+ を "name" という名前のパス変数として一致させます

"/projects/{project:[a-z]}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"+

{*path}

パスの最後まで 0 個以上のパスセグメントに一致し、"path" という名前の変数としてキャプチャーします

"/resources/{*file}" matches "/resources/images/file.png" and captures file=/images/file.png

次の例に示すように、キャプチャーされた URI 変数には @PathVariable を使用してアクセスできます。

  • Java

  • Kotlin

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
	// ...
}
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
	// ...
}

次の例に示すように、クラスおよびメソッドレベルで URI 変数を宣言できます。

  • Java

  • Kotlin

@Controller
@RequestMapping("/owners/{ownerId}") (1)
public class OwnerController {

	@GetMapping("/pets/{petId}") (2)
	public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
		// ...
	}
}
1 クラスレベルの URI マッピング。
2 メソッドレベルの URI マッピング。
@Controller
@RequestMapping("/owners/{ownerId}") (1)
class OwnerController {

	@GetMapping("/pets/{petId}") (2)
	fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
		// ...
	}
}
1 クラスレベルの URI マッピング。
2 メソッドレベルの URI マッピング。

URI 変数は自動的に適切な型に変換されるか、TypeMismatchException が発生します。単純型(intlongDate など)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および DataBinder を参照してください。

URI 変数には明示的に名前を付けることができますが (たとえば、@PathVariable("customId"))、名前が同じで、コードを -parameters コンパイラーフラグでコンパイルする場合は、その詳細を省略できます。

構文 {*varName} は、0 個以上の残りのパスセグメントに一致する URI 変数を宣言します。たとえば、/resources/{*path} は /resources/ のすべてのファイルと一致し、"path" 変数は /resources の完全なパスをキャプチャーします。

構文 {varName:regex} は、構文 {varName:regex} を持つ正規表現で URI 変数を宣言します。例: /spring-web-3.0.5.jar の URL を指定すると、次のメソッドは名前、バージョン、ファイル拡張子を抽出します。

  • Java

  • Kotlin

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
	// ...
}
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable version: String, @PathVariable ext: String) {
	// ...
}

URI パスパターンには、起動時にローカル、システム、環境、その他のプロパティソースに対して PropertySourcesPlaceholderConfigurer を介して解決される埋め込み ${…​} プレースホルダーを含めることもできます。これを使用して、たとえば、外部構成に基づいてベース URL をパラメーター化できます。

Spring WebFlux は、URI パス一致サポートのために PathPattern および PathPatternParser を使用します。両方のクラスは spring-web にあり、実行時に多数の URI パスパターンが一致する Web アプリケーションの HTTP URL パスで使用するために特別に設計されています。

Spring WebFlux はサフィックスパターンマッチングをサポートしていません — Spring MVC とは異なり、/person などのマッピングは /person.* にも一致します。URL ベースのコンテンツネゴシエーションでは、必要に応じて、クエリパラメーターを使用することをお勧めします。クエリパラメーターは、より単純で、より明示的で、URL パスベースのエクスプロイトに対して脆弱ではありません。

パターン比較

複数のパターンが URL に一致する場合、比較して最適な一致を見つける必要があります。これは、より具体的なパターンを探す PathPattern.SPECIFICITY_COMPARATOR で行われます。

すべてのパターンについて、URI 変数とワイルドカードの数に基づいてスコアが計算されます。URI 変数のスコアはワイルドカードよりも低くなります。合計スコアの低いパターンが優先されます。2 つのパターンのスコアが同じ場合、長い方が選択されます。

キャッチオールパターン(たとえば、**{*varName})はスコアリングから除外され、代わりに常に最後にソートされます。2 つのパターンが両方ともキャッチオールである場合、長い方が選択されます。

消費可能なメディア型

次の例に示すように、リクエストの Content-Type に基づいてリクエストマッピングを絞り込むことができます。

  • Java

  • Kotlin

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
	// ...
}
@PostMapping("/pets", consumes = ["application/json"])
fun addPet(@RequestBody pet: Pet) {
	// ...
}

消費属性は否定表現もサポートしています。たとえば、!text/plain は text/plain 以外のコンテンツ型を意味します。

クラスレベルで共有 consumes 属性を宣言できます。ただし、他のほとんどのリクエストマッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの consumes 属性は、クラスレベルの宣言を継承するのではなくオーバーライドします。

MediaType は、APPLICATION_JSON_VALUE や APPLICATION_XML_VALUE など、一般的に使用されるメディア型に定数を提供します。

生産可能なメディア型

次の例に示すように、Accept リクエストヘッダーとコントローラーメソッドが生成するコンテンツ型のリストに基づいて、リクエストマッピングを絞り込むことができます。

  • Java

  • Kotlin

@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
	// ...
}
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
	// ...
}

メディア型は文字セットを指定できます。否定表現がサポートされています。たとえば、!text/plain は text/plain 以外のコンテンツ型を意味します。

クラスレベルで共有 produces 属性を宣言できます。ただし、他のほとんどのリクエストマッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの produces 属性は、クラスレベルの宣言を継承するのではなくオーバーライドします。

MediaType は、一般的に使用されるメディア型に定数を提供します。APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE

パラメーターとヘッダー

クエリパラメーターの条件に基づいてリクエストマッピングを絞り込むことができます。クエリパラメーターの存在(myParam)、その不在(!myParam)、特定の値(myParam=myValue)をテストできます。次の例では、値を持つパラメーターをテストします。

  • Java

  • Kotlin

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
	// ...
}
1myParam が myValue と等しいことを確認してください。
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
1myParam が myValue と等しいことを確認してください。

次の例に示すように、リクエストヘッダー条件で同じものを使用することもできます。

  • Java

  • Kotlin

@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
	// ...
}
1myHeader が myValue と等しいことを確認してください。
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
1myHeader が myValue と等しいことを確認してください。

HTTP HEAD, OPTIONS

@GetMapping および @RequestMapping(method=HttpMethod.GET) は、リクエストマッピングの目的で HTTP HEAD を透過的にサポートします。コントローラーのメソッドを変更する必要はありません。HttpHandler サーバーアダプターに適用されるレスポンスラッパーは、Content-Length ヘッダーが実際にレスポンスに書き込まずに書き込まれたバイト数に設定されるようにします。

デフォルトでは、HTTP OPTIONS は、Allow レスポンスヘッダーを、一致する URL パターンを持つすべての @RequestMapping メソッドにリストされている HTTP メソッドのリストに設定することによって処理されます。

HTTP メソッド宣言のない @RequestMapping の場合、Allow ヘッダーは GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS に設定されます。コントローラーメソッドは、サポートされている HTTP メソッドを常に宣言する必要があります(たとえば、HTTP メソッド固有のバリアント(@GetMapping@PostMapping など)を使用するなど)。

@RequestMapping メソッドを明示的に HTTP HEAD および HTTP OPTIONS にマップできますが、これは一般的なケースでは必要ありません。

カスタムアノテーション

Spring WebFlux は、リクエストマッピングのための構成されたアノテーションの使用をサポートします。これらは、それ自体が @RequestMapping でメタアノテーションが付けられ、より狭く、より具体的な目的で @RequestMapping 属性のサブセット(またはすべて)を再宣言するように構成されたアノテーションです。

@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping は、合成されたアノテーションの例です。これらが提供されるのは、おそらく、デフォルトですべての HTTP メソッドに一致する @RequestMapping を使用するのではなく、ほとんどのコントローラーメソッドを特定の HTTP メソッドにマップする必要があるためです。合成されたアノテーションを実装する方法の例が必要な場合は、それらがどのように宣言されているかを確認してください。

@RequestMapping は、同じ要素 (クラス、インターフェース、メソッド) で宣言されている他の @RequestMapping アノテーションと組み合わせて使用することはできません。同じ要素で複数の @RequestMapping アノテーションが検出された場合、警告がログに記録され、最初のマッピングのみが使用されます。これは、@GetMapping@PostMapping などの合成 @RequestMapping アノテーションにも当てはまります。

Spring WebFlux は、カスタムリクエストマッチングロジックを持つカスタムリクエストマッピング属性もサポートしています。これは、RequestMappingHandlerMapping のサブクラス化と getCustomMethodCondition メソッドのオーバーライドを必要とする、より高度なオプションです。カスタム属性を確認して、独自の RequestCondition を返すことができます。

明示的な登録

ハンドラーメソッドをプログラムで登録できます。これは、動的な登録や、異なる URL での同じハンドラーの異なるインスタンスなどの高度なケースに使用できます。次の例は、その方法を示しています。

  • Java

  • Kotlin

@Configuration
public class MyConfig {

	@Autowired
	public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) (1)
			throws NoSuchMethodException {

		RequestMappingInfo info = RequestMappingInfo
				.paths("/user/{id}").methods(RequestMethod.GET).build(); (2)

		Method method = UserHandler.class.getMethod("getUser", Long.class); (3)

		mapping.registerMapping(info, handler, method); (4)
	}

}
1 ターゲットハンドラーとコントローラーのハンドラーマッピングを挿入します。
2 リクエストマッピングメタデータを準備します。
3 ハンドラーメソッドを取得します。
4 登録を追加します。
@Configuration
class MyConfig {

	@Autowired
	fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { (1)

		val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() (2)

		val method = UserHandler::class.java.getMethod("getUser", Long::class.java) (3)

		mapping.registerMapping(info, handler, method) (4)
	}
}
1 ターゲットハンドラーとコントローラーのハンドラーマッピングを挿入します。
2 リクエストマッピングメタデータを準備します。
3 ハンドラーメソッドを取得します。
4 登録を追加します。

@HttpExchange

@HttpExchange の主な目的は、生成されたプロキシを使用して HTTP クライアントコードを抽象化することですが、そのようなアノテーションが配置される HTTP インターフェースは、クライアントとサーバーの使用に中立的な契約です。クライアントコードを簡素化することに加えて、HTTP インターフェースはサーバーがクライアントアクセス用に API を公開する便利なメソッドである場合もあります。これはクライアントとサーバー間の結合の増加につながり、特にパブリック API にとっては良い選択ではないことがよくありますが、内部 API にとってはまさにゴールとなる可能性があります。これは Spring Cloud で一般的に使用されるアプローチであり、コントローラークラスでのサーバー側の処理のために @RequestMapping の代替として @HttpExchange がサポートされるのはこのためです。

例:

  • Java

  • Kotlin

@HttpExchange("/persons")
interface PersonService {

	@GetExchange("/{id}")
	Person getPerson(@PathVariable Long id);

	@PostExchange
	void add(@RequestBody Person person);
}

@RestController
class PersonController implements PersonService {

	public Person getPerson(@PathVariable Long id) {
		// ...
	}

	@ResponseStatus(HttpStatus.CREATED)
	public void add(@RequestBody Person person) {
		// ...
	}
}
@HttpExchange("/persons")
interface PersonService {

	@GetExchange("/{id}")
	fun getPerson(@PathVariable id: Long): Person

	@PostExchange
	fun add(@RequestBody person: Person)
}

@RestController
class PersonController : PersonService {

	override fun getPerson(@PathVariable id: Long): Person {
		// ...
	}

	@ResponseStatus(HttpStatus.CREATED)
	override fun add(@RequestBody person: Person) {
		// ...
	}
}

@HttpExchange と @RequestMapping には違いがあります。@RequestMapping はパスパターン、HTTP メソッドなどによって任意の数のリクエストにマップできますが、@HttpExchange は具体的な HTTP メソッド、パス、コンテンツ型を使用して単一のエンドポイントを宣言します。

メソッドパラメーターと戻り値については、通常、@HttpExchange は @RequestMapping が行うメソッドパラメーターのサブセットをサポートします。特に、サーバー側固有のパラメーター型は除外されます。詳細は @HttpExchange@RequestMapping の一覧を参照してください。