對映請求

本節討論註解控制器的請求對映。

@RequestMapping

@RequestMapping 註解用於將請求對映到控制器方法。它具有透過 URL、HTTP 方法、請求引數、請求頭和媒體型別進行匹配的各種屬性。您可以在類級別使用它來表達共享對映,或者在方法級別使用它來縮小到特定端點對映。

還有 @RequestMapping 的 HTTP 方法特定快捷方式變體:

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

前面的註解是自定義註解,它們之所以提供,是因為可以說大多數控制器方法應該對映到特定的 HTTP 方法,而不是使用 @RequestMapping,後者預設匹配所有 HTTP 方法。同時,仍然需要在類級別使用 @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 模式和萬用字元對映請求:

模式 描述 示例

spring

字面模式

"/spring" 匹配 "/spring"

?

匹配一個字元

"/pages/t?st.html" 匹配 "/pages/test.html""/pages/t3st.html"

*

匹配路徑段中的零個或多個字元

"/resources/*.png" 匹配 "/resources/file.png"

"/projects/*/versions" 匹配 "/projects/spring/versions" 但不匹配 "/projects/spring/boot/versions"

"/projects/*" 匹配 "/projects/spring" 但不匹配 "/projects",因為路徑段不存在。

**

匹配零個或多個路徑段

"/resources/**" 匹配 "/resources""/resources/file.png""/resources/images/file.png"

"/**/info" 匹配 "/info""/spring/info""/spring/framework/info"

"/resources/**/file.png" 無效,因為 ** 不允許出現在路徑中間。

"/**/spring/**" 不允許,因為每個模式只允許一個 **/{*path} 例項。

{name}

類似於 *,但也將路徑段捕獲為名為“name”的變數

"/projects/{project}/versions" 匹配 "/projects/spring/versions" 並捕獲 project=spring

"/projects/{project}/versions" 不匹配 "/projects/spring/framework/versions",因為它捕獲單個路徑段。

{name:[a-z]+}

將正則表示式 "[a-z]+" 匹配為名為“name”的路徑變數

"/projects/{project:[a-z]+}/versions" 匹配 "/projects/spring/versions" 但不匹配 "/projects/spring1/versions"

{*path}

類似於 **,但也捕獲路徑段作為名為“path”的變數

"/resources/{*file}" 匹配 "/resources/images/file.png" 並捕獲 file=/images/file.png

"{*path}/resources" 匹配 "/spring/framework/resources" 並捕獲 path=/spring/framework

"/resources/{*path}/file.png" 無效,因為 {*path} 不允許出現在路徑中間。

"/{*path}/spring/**" 不允許,因為每個模式只允許一個 **/{*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。預設支援簡單型別(intlongDate 等),您可以註冊對任何其他資料型別的支援。請參閱型別轉換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 非常有用。

  • SpEL 表示式 #{…​}

Spring WebFlux 使用 PathPatternPathPatternParser 來支援 URI 路徑匹配。這兩個類都位於 spring-web 中,並且專門設計用於 Web 應用程式中的 HTTP URL 路徑,其中在執行時匹配大量 URI 路徑模式。

Spring WebFlux 不支援字尾模式匹配——與 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_VALUEAPPLICATION_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) {
	// ...
}
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

API 版本

沒有標準的方法來指定 API 版本,因此當您在 WebFlux 配置中啟用 API 版本控制時,您需要指定如何解析版本。WebFlux 配置會建立一個 ApiVersionStrategy,該策略又用於對映請求。

啟用 API 版本控制後,您就可以開始使用版本對映請求了。@RequestMappingversion 屬性支援以下內容:

  • 無值 — 匹配任何版本

  • 固定版本("1.2")——僅匹配給定版本

  • 基線版本 ("1.2+") — 匹配給定版本及更高版本

如果多個控制器方法具有小於或等於請求版本的版本,則將考慮其中最高且最接近請求版本的那個,實際上會取代其餘的。

為了說明這一點,請考慮以下對映:

  • Java

@RestController
@RequestMapping("/account/{id}")
public class AccountController {

	@GetMapping (1)
	public Account getAccount() {
	}

	@GetMapping(version = "1.1") (2)
	public Account getAccount1_1() {
	}

	@GetMapping(version = "1.2+") (3)
	public Account getAccount1_2() {
	}

	@GetMapping(version = "1.5") (4)
	public Account getAccount1_5() {
	}
}
1 匹配任何版本
2 匹配版本 1.1
3 匹配版本 1.2 及更高版本
4 匹配版本 1.5

對於版本 "1.3" 的請求:

  • (1) 匹配,因為它匹配任何版本

  • (2) 不匹配

  • (3) 匹配,因為它匹配 1.2 及更高版本,並被選擇為最高匹配

  • (4) 更高且不匹配

對於版本 "1.5" 的請求:

  • (1) 匹配,因為它匹配任何版本

  • (2) 不匹配

  • (3) 匹配,因為它匹配 1.2 及更高版本

  • (4) 匹配,並被選擇為最高匹配

版本 "1.6" 的請求沒有匹配項。(1) 和 (3) 匹配,但被 (4) 取代,(4) 只允許嚴格匹配,因此不匹配。在這種情況下,NotAcceptableApiVersionException 會導致 400 響應。

以上假設請求版本是“支援”的版本,否則會失敗。

有關底層基礎設施和 API 版本控制支援的更多詳細資訊,請參閱API 版本控制

HTTP HEAD、OPTIONS

@GetMapping@RequestMapping(method=HttpMethod.GET) 對於請求對映目的透明地支援 HTTP HEAD。控制器方法無需更改。在 HttpHandler 伺服器介面卡中應用的響應包裝器確保將 Content-Length 頭設定為寫入的位元組數,而無需實際寫入響應。

預設情況下,HTTP OPTIONS 透過將 Allow 響應頭設定為所有具有匹配 URL 模式的 @RequestMapping 方法中列出的 HTTP 方法列表來處理。

對於沒有 HTTP 方法宣告的 @RequestMappingAllow 頭設定為 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 註解,將記錄警告,並且只使用第一個對映。這也適用於組合的 @RequestMapping 註解,例如 @GetMapping@PostMapping 等。

Spring WebFlux 還支援帶有自定義請求匹配邏輯的自定義請求對映屬性。這是一個更高階的選項,需要子類化 RequestMappingHandlerMapping 並覆蓋 getCustomMethodCondition 方法,在該方法中您可以檢查自定義屬性並返回自己的 RequestCondition

顯式註冊

您可以以程式設計方式註冊 Handler 方法,這可用於動態註冊或高階情況,例如在不同 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 的列表。

© . This site is unofficial and not affiliated with VMware.