リクエストマッピング
このセクションでは、アノテーション付きコントローラーのリクエストマッピングについて説明します。
@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 文字に一致 |
|
| パスセグメント内の 0 個以上の文字に一致します |
|
| パスの終わりまで 0 個以上のパスセグメントに一致します |
|
| パスセグメントを照合し、"name" という名前の変数としてキャプチャーします |
|
| 正規表現 |
|
| パスの最後まで 0 個以上のパスセグメントに一致し、"path" という名前の変数としてキャプチャーします |
|
次の例に示すように、キャプチャーされた 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
が発生します。単純型(int
、long
、Date
など)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および 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_VALUE 、APPLICATION_XML_VALUE |
パラメーターとヘッダー
クエリパラメーターの条件に基づいてリクエストマッピングを絞り込むことができます。クエリパラメーターの存在(myParam
)、その不在(!myParam
)、特定の値(myParam=myValue
)をテストできます。次の例では、値を持つパラメーターをテストします。
Java
Kotlin
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
// ...
}
1 | myParam が myValue と等しいことを確認してください。 |
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
// ...
}
1 | myParam が myValue と等しいことを確認してください。 |
次の例に示すように、リクエストヘッダー条件で同じものを使用することもできます。
Java
Kotlin
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
// ...
}
1 | myHeader が myValue と等しいことを確認してください。 |
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
// ...
}
1 | myHeader が 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 の一覧を参照してください。