對映請求
本節討論註解控制器的請求對映。
@RequestMapping
@RequestMapping
註解用於將請求對映到控制器方法。它有各種屬性,可以根據 URL、HTTP 方法、請求引數、請求頭和媒體型別進行匹配。您可以在類級別使用它來表達共享對映,或者在方法級別使用它來細化到特定的端點對映。
還有針對特定 HTTP 方法的 @RequestMapping
快捷變體:
-
@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" 的變數 |
|
|
將正則表示式 |
|
|
匹配路徑末尾的零個或多個路徑段,並將其捕獲為名為 "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 應用中大量 URI 路徑模式的 HTTP URL 路徑。 |
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
。
顯式註冊
您可以程式化註冊 Handler 方法,這可用於動態註冊或高階用例,例如在不同 URL 下使用同一 Handler 的不同例項。以下示例演示瞭如何執行此操作:
-
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 | 注入目標 Handler 和控制器的 Handler 對映。 |
2 | 準備請求對映元資料。 |
3 | 獲取 Handler 方法。 |
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 | 注入目標 Handler 和控制器的 Handler 對映。 |
2 | 準備請求對映元資料。 |
3 | 獲取 Handler 方法。 |
4 | 添加註冊。 |
@HttpExchange
雖然 @HttpExchange
的主要目的是透過生成的代理來抽象 HTTP 客戶端程式碼,但放置此類註解的 HTTP Interface 是一個對客戶端與伺服器使用保持中立的契約。除了簡化客戶端程式碼外,在某些情況下,HTTP Interface 也可能是伺服器暴露其 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 的列表。