請求對應
本節討論註解控制器的請求對應。
@RequestMapping
@RequestMapping
註解用於將請求對應到控制器方法。它具有各種屬性,可以通過 URL、HTTP 方法、請求參數、標頭和媒體類型進行匹配。您可以在類別層級使用它來表達共用對應,或在方法層級使用它來縮小到特定的端點對應。
還有 @RequestMapping
的 HTTP 方法特定快捷方式變體
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
前述註解是提供的自訂註解,因為可以說,大多數控制器方法應該對應到特定的 HTTP 方法,而不是使用預設情況下與所有 HTTP 方法都匹配的 @RequestMapping
。同時,在類別層級仍然需要 @RequestMapping
來表達共用對應。
@RequestMapping 不能與在同一元素(類別、介面或方法)上宣告的其他 @RequestMapping 註解結合使用。如果在同一元素上偵測到多個 @RequestMapping 註解,將記錄警告,並且只會使用第一個對應。這也適用於組成的 @RequestMapping 註解,例如 @GetMapping 、@PostMapping 等。 |
以下範例使用類型和方法層級對應
-
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 模式和萬用字元來對應請求
模式 | 描述 | 範例 |
---|---|---|
|
匹配一個字元 |
|
|
匹配路徑區段內零或多個字元 |
|
|
匹配零或多個路徑區段,直到路徑結尾 |
|
|
匹配路徑區段並將其捕獲為名為 "name" 的變數 |
|
|
將 regexp |
|
|
匹配零或多個路徑區段,直到路徑結尾,並將其捕獲為名為 "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}
宣告一個 URI 變數,該變數匹配零或多個剩餘路徑區段。例如,/resources/{*path}
匹配 /resources/
下的所有檔案,而 "path"
變數捕獲 /resources
下的完整路徑。
語法 {varName:regex}
宣告一個帶有正則表達式的 URI 變數,其語法為:{varName:regex}
。例如,給定 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 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 使用 PathPattern 和 PathPatternParser 來支援 URI 路徑匹配。這兩個類別都位於 spring-web 中,並且專門設計用於 Web 應用程式中的 HTTP URL 路徑,在這些應用程式中,會在運行時匹配大量 URI 路徑模式。 |
Spring WebFlux 不支援後綴模式匹配 — 與 Spring MVC 不同,在 Spring MVC 中,諸如 /person
之類的對應也匹配 /person.*
。對於基於 URL 的內容協商,如果需要,我們建議使用查詢參數,這更簡單、更明確,並且更不容易受到基於 URL 路徑的漏洞利用的影響。
模式比較
當多個模式匹配 URL 時,必須對它們進行比較以找到最佳匹配。這是通過 PathPattern.SPECIFICITY_COMPARATOR
完成的,它尋找更具體的模式。
對於每個模式,都會計算一個分數,該分數基於 URI 變數和萬用字元的數量,其中 URI 變數的分數低於萬用字元。總分較低的模式獲勝。如果兩個模式具有相同的分數,則選擇較長的模式。
捕獲所有模式(例如,**
、{*varName}
)被排除在評分之外,並且始終排在最後。如果兩個模式都是捕獲所有模式,則選擇較長的模式。
可消費的媒體類型
您可以根據請求的 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) {
// ...
}
consumes
屬性也支援否定運算式 — 例如,!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 方法,而不是使用預設情況下與所有 HTTP 方法都匹配的 @RequestMapping
。如果您需要有關如何實作組合註解的範例,請查看它們的宣告方式。
@RequestMapping 不能與在同一元素(類別、介面或方法)上宣告的其他 @RequestMapping 註解結合使用。如果在同一元素上偵測到多個 @RequestMapping 註解,將記錄警告,並且只會使用第一個對應。這也適用於組成的 @RequestMapping 註解,例如 @GetMapping 、@PostMapping 等。 |
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 中常用的方法,這也是為什麼 @HttpExchange
作為 @RequestMapping
的替代方案受到支援,用於控制器類別中的伺服器端處理。
例如
-
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
也支援 headers()
參數,該參數接受類似於客戶端 @RequestMapping(headers={})
中的 "name=value" 對。在伺服器端,這擴展到 @RequestMapping
支援的完整語法。