リクエストマッピング
このセクションでは、アノテーション付きコントローラーのリクエストマッピングについて説明します。
@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 パターン
@RequestMapping
メソッドは、URL パターンを使用してマッピングできます。2 つの選択肢があります。
PathPattern
—PathContainer
としても事前に解析された URL パスと照合される事前に解析されたパターン。このソリューションは Web 使用向けに設計されており、エンコーディングとパスパラメーターを効果的に処理し、効率的に一致します。AntPathMatcher
— 文字列パターンを文字列パスと照合します。これは、Spring 構成でもクラスパス、ファイルシステム、その他の場所のリソースを選択するために使用される元のソリューションです。これは効率が悪く、文字列パスの入力は、URL のエンコーディングやその他の課題を効果的に処理するための課題です。
PathPattern
は、Web アプリケーションに推奨されるソリューションであり、Spring WebFlux で唯一の選択肢です。バージョン 5.3 から Spring MVC での使用が有効になり、バージョン 6.0 からデフォルトで有効になります。パスマッチングオプションのカスタマイズについては、MVC 設定を参照してください。
PathPattern
は、AntPathMatcher
と同じパターン構文をサポートします。さらに、パスの末尾の 0 個以上のパスセグメントに一致するキャプチャーパターン ( {*spring}
など) もサポートします。また、PathPattern
は、複数のパスセグメントに一致する **
の使用をパターンの末尾でのみ許可するように制限します。これにより、特定のリクエストに最適な一致パターンを選択するときに、多くのあいまいさが排除されます。完全なパターン構文については、PathPattern (Javadoc) および AntPathMatcher (Javadoc) を参照してください。
いくつかのパターン例:
"/resources/ima?e.png"
- パスセグメントの 1 文字に一致"/resources/*.png"
- パスセグメントの 0 個以上の文字に一致"/resources/**"
- 複数のパスセグメントに一致"/projects/{project}/versions"
- パスセグメントを一致させ、変数としてキャプチャーする"/projects/{project:[a-z]+}/versions"
- 変数を正規表現に一致させてキャプチャーする
キャプチャーされた 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}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {
@GetMapping("/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
}
URI 変数は適切な型に自動的に変換されるか、TypeMismatchException
が発生します。単純型(int
、long
、Date
など)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および DataBinder
を参照してください。
URI 変数に明示的に名前を付けることができますが (たとえば、@PathVariable("customId")
)、名前が同じで、コードが -parameters
コンパイラーフラグでコンパイルされている場合は、その詳細を省略できます。
構文 {varName:regex}
は、{varName:regex}
の構文を持つ正規表現で URI 変数を宣言します。例: URL "/spring-web-3.0.5.jar"
を指定すると、次のメソッドは名前、バージョン、ファイル拡張子を抽出します。
Java
Kotlin
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable name: String, @PathVariable version: String, @PathVariable ext: String) {
// ...
}
URI パスパターンには、PropertySourcesPlaceholderConfigurer
をローカル、システム、環境、その他のプロパティソースに対して使用することにより、起動時に解決される ${…}
プレースホルダーを埋め込むこともできます。これを使用して、たとえば、外部設定に基づいてベース URL をパラメーター化できます。
パターン比較
複数のパターンが URL に一致する場合は、最適なものを選択する必要があります。これは、解析された PathPattern
の使用が有効になっているかどうかに応じて、次のいずれかで実行されます。
どちらも、より具体的なパターンを上位にしてパターンを並べ替えるのに役立ちます。URI 変数の数 (1 としてカウント)、単一のワイルドカード (1 としてカウント)、および二重ワイルドカード (2 としてカウント) の数が少ないほど、パターンはより具体的になります。スコアが等しい場合、より長いパターンが選択されます。同じスコアと長さの場合、ワイルドカードよりも多くの URI 変数を含むパターンが選択されます。
デフォルトのマッピングパターン(/**
)はスコアリングから除外され、常に最後にソートされます。また、プレフィックスパターン(/public/**
など)は、二重ワイルドカードを持たない他のパターンよりも特定性が低いと見なされます。
詳細については、上記のパターンコンパレータへのリンクをたどってください。
サフィックスマッチ
5.3 以降、デフォルトで Spring MVC は .*
サフィックスパターンマッチングを実行しなくなりました。/person
にマップされたコントローラーも暗黙的に /person.*
にマップされます。結果として、パス拡張は、レスポンスのためにリクエストされたコンテンツ型を解釈するために使用されなくなりました (たとえば、/person.pdf
、/person.xml
など)。
ブラウザーが一貫して解釈するのが困難な Accept
ヘッダーを送信するために使用した場合、この方法でファイル拡張子を使用する必要がありました。現時点では、もはや必要ではなく、Accept
ヘッダーを使用することをお勧めします。
時間の経過とともに、ファイル名拡張子の使用はさまざまな点で問題があることが証明されています。URI 変数、パスパラメーター、URI エンコードを使用してオーバーレイすると、あいまいさが生じる可能性があります。URL ベースの認可とセキュリティ(詳細については次のセクションを参照)についての推論もより困難になります。
5.3 より前のバージョンでパス拡張機能の使用を完全に無効にするには、次のように設定します。
useSuffixPatternMatching(false)
、PathMatchConfigurer を参照favorPathExtension(false)
、ContentNegotiationConfigurer を参照
"Accept"
ヘッダー以外でコンテンツ型をリクエストする方法があると、たとえばブラウザーで URL を入力するときなどに便利です。パス拡張子の安全な代替手段は、クエリパラメーター戦略を使用することです。ファイル拡張子を使用する必要がある場合は、ContentNegotiationConfigurer の mediaTypes
プロパティを通じて明示的に登録された拡張子のリストに制限することを検討してください。
サフィックスマッチと RFD
反射ファイルダウンロード(RFD)攻撃は、レスポンスに反映されるリクエスト入力(クエリパラメーターや URI 変数など)に依存するという点で XSS に似ています。ただし、JavaScript を HTML に挿入する代わりに、ブラウザーを切り替えてダウンロードを実行し、後でダブルクリックしたときにレスポンスを実行可能なスクリプトとして処理することに依存します。
Spring MVC では、@ResponseBody
および ResponseEntity
メソッドが危険にさらされます。これは、クライアントが URL パス拡張を介してリクエストできるさまざまなコンテンツ型をレンダリングできるためです。サフィックスパターンマッチングを無効にし、コンテンツネゴシエーションにパス拡張を使用すると、リスクは低下しますが、RFD 攻撃を防ぐには不十分です。
RFD 攻撃を防ぐために、Spring MVC は、レスポンス本文をレンダリングする前に、固定された安全なダウンロードファイルを提案する Content-Disposition:inline;filename=f.txt
ヘッダーを追加します。これは、URL パスに、コンテンツネゴシエーションに対して安全として許可されておらず、明示的に登録されていないファイル拡張子が含まれている場合にのみ行われます。ただし、URL をブラウザーに直接入力すると、副作用が生じる可能性があります。
多くの一般的なパス拡張は、デフォルトで安全として許可されています。カスタム HttpMessageConverter
実装を使用するアプリケーションは、コンテンツネゴシエーションのファイル拡張子を明示的に登録して、それらの拡張子に Content-Disposition
ヘッダーが追加されないようにすることができます。コンテンツタイプを参照してください。
RFD に関連する追加の推奨事項については、CVE-2015-5211 (英語) を参照してください。
消費可能なメディア型
次の例に示すように、リクエストの Content-Type
に基づいてリクエストマッピングを絞り込むことができます。
Java
Kotlin
@PostMapping(path = "/pets", consumes = "application/json") (1)
public void addPet(@RequestBody Pet pet) {
// ...
}
1 | consumes 属性を使用して、コンテンツ型ごとにマッピングを絞り込みます。 |
@PostMapping("/pets", consumes = ["application/json"]) (1)
fun addPet(@RequestBody pet: Pet) {
// ...
}
1 | consumes 属性を使用して、コンテンツ型ごとにマッピングを絞り込みます。 |
consumes
属性は否定表現もサポートします。たとえば、!text/plain
は text/plain
以外のコンテンツ型を意味します。
クラスレベルで共有 consumes
属性を宣言できます。ただし、他のほとんどのリクエストマッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの consumes
属性はクラスレベルの宣言を継承するのではなくオーバーライドします。
MediaType は、APPLICATION_JSON_VALUE や APPLICATION_XML_VALUE などの一般的に使用されるメディア型に定数を提供します。 |
生産可能なメディア型
次の例に示すように、Accept
リクエストヘッダーとコントローラーメソッドが生成するコンテンツ型のリストに基づいて、リクエストマッピングを絞り込むことができます。
Java
Kotlin
@GetMapping(path = "/pets/{petId}", produces = "application/json") (1)
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
1 | produces 属性を使用して、コンテンツ型ごとにマッピングを絞り込みます。 |
@GetMapping("/pets/{petId}", produces = ["application/json"]) (1)
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
// ...
}
1 | produces 属性を使用して、コンテンツ型ごとにマッピングを絞り込みます。 |
メディア型は文字セットを指定できます。否定式がサポートされています。たとえば、!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 を透過的にサポートします。コントローラーのメソッドを変更する必要はありません。jakarta.servlet.http.HttpServlet
で適用されるレスポンスラッパーは、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 MVC は、リクエストマッピングのための構成済みアノテーションの使用をサポートしています。それらは、それ自体が @RequestMapping
でメタアノテーションが付けられたアノテーションであり、@RequestMapping
属性のサブセット (またはすべて) をより狭く、より具体的な目的で再宣言するように構成されています。
@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
、@PatchMapping
は、合成されたアノテーションの例です。これらが提供されるのは、おそらく、デフォルトですべての HTTP メソッドに一致する @RequestMapping
を使用するのではなく、ほとんどのコントローラーメソッドを特定の HTTP メソッドにマップする必要があるためです。合成されたアノテーションを実装する方法の例が必要な場合は、それらがどのように宣言されているかを確認してください。
@RequestMapping は、同じ要素 (クラス、インターフェース、メソッド) で宣言されている他の @RequestMapping アノテーションと組み合わせて使用することはできません。同じ要素で複数の @RequestMapping アノテーションが検出された場合、警告がログに記録され、最初のマッピングのみが使用されます。これは、@GetMapping 、@PostMapping などの合成 @RequestMapping アノテーションにも当てはまります。 |
Spring MVC は、カスタムリクエストマッチングロジックを使用したカスタムリクエストマッピング属性もサポートしています。これは、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 の一覧を参照してください。
@HttpExchange
は、クライアント側の @RequestMapping(headers={})
と同様に "name=value"
-like ペアを受け入れる headers()
パラメーターもサポートします。サーバー側では、これは @RequestMapping
がサポートする完全な構文に拡張されます。