Mapping Requests

本部分讨论了带注解控制器的请求映射。

This section discusses request mapping for annotated controllers.

@RequestMapping

@RequestMapping 注释用于将请求映射到控制器方法。它具有根据 URL、HTTP 方法、请求参数、标头和媒体类型进行匹配的各种属性。您可以在类级别使用它来表达共享映射,或者在方法级别使用它来缩小范围到一个特定端点映射。

The @RequestMapping annotation is used to map requests to controllers methods. It has various attributes to match by URL, HTTP method, request parameters, headers, and media types. You can use it at the class level to express shared mappings or at the method level to narrow down to a specific endpoint mapping.

@RequestMapping 还存在 HTTP 方法特定的快捷方式变体:

There are also HTTP method specific shortcut variants of @RequestMapping:

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

前面的注解是 Custom Annotations,之所以提供这些注解,是因为,可以说,大多数控制方法都应该映射到特定的 HTTP 方法,而不是使用 @RequestMapping,后者默认匹配所有 HTTP 方法。同时,在类级别仍然需要 @RequestMapping 来表示共享映射。

The preceding annotations are Custom Annotations that are provided because, arguably, most controller methods should be mapped to a specific HTTP method versus using @RequestMapping, which, by default, matches to all HTTP methods. At the same time, a @RequestMapping is still needed at the class level to express shared mappings.

@RequestMapping 不能与在同一元素(类、接口或方法)上声明的其他 @RequestMapping 注释结合使用。如果在同一元素上检测到多个 @RequestMapping 注释,将记录一条警告,并且只会使用第一个映射。这也适用于 @GetMapping@PostMapping 等复合 @RequestMapping 注释。

@RequestMapping cannot be used in conjunction with other @RequestMapping annotations that are declared on the same element (class, interface, or method). If multiple @RequestMapping annotations are detected on the same element, a warning will be logged, and only the first mapping will be used. This also applies to composed @RequestMapping annotations such as @GetMapping, @PostMapping, etc.

以下示例使用类型和方法级映射:

The following example uses type and method level mappings:

  • 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 Patterns

可以使用 glob 模式和通配符映射请求:

You can map requests by using glob patterns and wildcards:

Pattern Description Example

?

Matches one character

"/pages/t?st.html" matches "/pages/test.html" and "/pages/t3st.html"

*

Matches zero or more characters within a path segment

"/resources/.png" matches "/resources/file.png"

"/projects//versions" matches "/projects/spring/versions" but does not match "/projects/spring/boot/versions"

**

Matches zero or more path segments until the end of the path

"/resources/" matches "/resources/file.png" and "/resources/images/file.png"

"/resources//file.png" is invalid as ** is only allowed at the end of the path.

{name}

Matches a path segment and captures it as a variable named "name"

"/projects/{project}/versions" matches "/projects/spring/versions" and captures project=spring

{name:[a-z]}+

Matches the regexp "[a-z]"+ as a path variable named "name"

"/projects/{project:[a-z]}/versions" matches "/projects/spring/versions" but not "/projects/spring1/versions"+

{*path}

Matches zero or more path segments until the end of the path and captures it as a variable named "path"

"/resources/{*file}" matches "/resources/images/file.png" and captures file=/images/file.png

可以使用 @PathVariable 访问捕获的 URI 变量,如下例所示:

Captured URI variables can be accessed with @PathVariable, as the following example shows:

  • 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 变量:

You can declare URI variables at the class and method levels, as the following example shows:

Java
@Controller
@RequestMapping("/owners/{ownerId}") (1)
public class OwnerController {

	@GetMapping("/pets/{petId}") (2)
	public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
		// ...
	}
}
1 Class-level URI mapping.
2 Method-level URI mapping.
Kotlin
@Controller
@RequestMapping("/owners/{ownerId}") (1)
class OwnerController {

	@GetMapping("/pets/{petId}") (2)
	fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
		// ...
	}
}
3 Class-level URI mapping.
4 Method-level URI mapping.

URI 变量自动转换为适当的类型或提出 @26。简单类型(@27、@28、@29 等)默认支持,而且您可以为任何其他数据类型注册支持。请参阅 @31 和 @32。

URI variables are automatically converted to the appropriate type or a TypeMismatchException is raised. Simple types (int, long, Date, and so on) are supported by default and you can register support for any other data type. See Type Conversion and DataBinder.

可以显式命名 URI 变量(例如,@PathVariable("customId")),但如果名称相同,并且使用 -parameters 编译器标志编译代码,则可以省去此详细信息。

URI variables can be named explicitly (for example, @PathVariable("customId")), but you can leave that detail out if the names are the same and you compile your code with the -parameters compiler flag.

语法 {*varName} 声明一个匹配零个或多个剩余路径段的 URI 变量。例如,/resources/{*path} 匹配 /resources/ 下的所有文件,并且 "path" 变量会捕获 /resources 下的完整路径。

The syntax {*varName} declares a URI variable that matches zero or more remaining path segments. For example /resources/{*path} matches all files under /resources/, and the "path" variable captures the complete path under /resources.

语法 {varName:regex} 声明一个具有以下语法正则表达式的 URI 变量:{varName:regex}。例如,给定 URL 为 /spring-web-3.0.5.jar,则以下方法会提取名称、版本和文件扩展名:

The syntax {varName:regex} declares a URI variable with a regular expression that has the syntax: {varName:regex}. For example, given a URL of /spring-web-3.0.5.jar, the following method extracts the name, version, and file extension:

  • 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 进行参数化。

URI path patterns can also have embedded ${…​} placeholders that are resolved on startup through PropertySourcesPlaceholderConfigurer against local, system, environment, and other property sources. You can use this to, for example, parameterize a base URL based on some external configuration.

Spring WebFlux 使用 PathPatternPathPatternParser 用于 URI 路径匹配支持。这两个类位于 spring-web 中,并专门设计用于在运行时匹配大量 URI 路径模式的 Web 应用程序中的 HTTP URL 路径。

Spring WebFlux uses PathPattern and the PathPatternParser for URI path matching support. Both classes are located in spring-web and are expressly designed for use with HTTP URL paths in web applications where a large number of URI path patterns are matched at runtime.

Spring WebFlux 不支持后缀模式匹配——与 Spring MVC 不同,后者中像 /person 这样的映射也匹配 /person.*。如果需要基于 URL 的内容协商,我们建议使用查询参数,它更简单、更明确、也不太容易受到基于 URL 路径的攻击。

Spring WebFlux does not support suffix pattern matching — unlike Spring MVC, where a mapping such as /person also matches to /person.*. For URL-based content negotiation, if needed, we recommend using a query parameter, which is simpler, more explicit, and less vulnerable to URL path based exploits.

Pattern Comparison

当多个模式匹配某个 URL 时,必须比较它们以找到最佳匹配。此操作使用 PathPattern.SPECIFICITY_COMPARATOR 完成,后者会寻找更具体的模式。

When multiple patterns match a URL, they must be compared to find the best match. This is done with PathPattern.SPECIFICITY_COMPARATOR, which looks for patterns that are more specific.

对于每个模式,都会基于 URI 变量和通配符的数量计算一个分数,其中 URI 变量的分数低于通配符。总分较低的模式获胜。如果两个模式的分数相同,则选择较长的模式。

For every pattern, a score is computed, based on the number of URI variables and wildcards, where a URI variable scores lower than a wildcard. A pattern with a lower total score wins. If two patterns have the same score, the longer is chosen.

总括模式(例如 **{*varName})不包含在评分中,并且始终被排序到最后。如果两个模式都是总括模式,则选择较长的模式。

Catch-all patterns (for example, **, {*varName}) are excluded from the scoring and are always sorted last instead. If two patterns are both catch-all, the longer is chosen.

Consumable Media Types

您可以根据请求的 Content-Type 缩小请求映射的范围,如下例所示:

You can narrow the request mapping based on the Content-Type of the request, as the following example shows:

  • 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 的内容类型。

The consumes attribute also supports negation expressions — for example, !text/plain means any content type other than text/plain.

可以在类级别声明共享的 consumes 属性。然而,与大多数其他请求映射属性不同,在类级别使用时,方法级别的 consumes 属性会替代类级别的声明,而不是进行扩展。

You can declare a shared consumes attribute at the class level. Unlike most other request mapping attributes, however, when used at the class level, a method-level consumes attribute overrides rather than extends the class-level declaration.

MediaType 提供常用媒体类型的常量——例如,APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE

MediaType provides constants for commonly used media types — for example, APPLICATION_JSON_VALUE and APPLICATION_XML_VALUE.

Producible Media Types

你可以根据 Accept 请求头和控制器方法生成的长度类型列表来缩小请求映射范围,如下例所示:

You can narrow the request mapping based on the Accept request header and the list of content types that a controller method produces, as the following example shows:

  • 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

The media type can specify a character set. Negated expressions are supported — for example, !text/plain means any content type other than text/plain.

您可以在类级别声明共享的 produces 属性。但是,与大多数其他请求映射属性不同,当在类级别使用时,方法级别的 produces 属性会覆盖而不是扩展类级别声明。

You can declare a shared produces attribute at the class level. Unlike most other request mapping attributes, however, when used at the class level, a method-level produces attribute overrides rather than extend the class level declaration.

MediaType 提供常用媒体类型的常量——例如,APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE

MediaType provides constants for commonly used media types — e.g. APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE.

Parameters and Headers

您可以基于查询参数条件缩小请求映射。您可以测试查询参数是否存在 (myParam),缺少 (!myParam),或存在特定值 (myParam=myValue)。以下示例测试带值的某个参数:

You can narrow request mappings based on query parameter conditions. You can test for the presence of a query parameter (myParam), for its absence (!myParam), or for a specific value (myParam=myValue). The following examples tests for a parameter with a value:

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
	// ...
}
1 Check that myParam equals myValue.
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
2 Check that myParam equals myValue.

你还可以使用与请求头的条件相同的条件,如下例所示:

You can also use the same with request header conditions, as the following example shows:

Java
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
	// ...
}
1 Check that myHeader equals myValue.
Kotlin
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
2 Check that myHeader equals myValue.

HTTP HEAD, OPTIONS

@GetMapping@RequestMapping(method=HttpMethod.GET) 会透明地支持 HTTP HEAD 以用于请求映射。控制器方法无需更改。应用到 HttpHandler 服务器适配器中的响应包装器会确保将 Content-Length 头部设置为已写入的字节数,而无需实际写入响应。

@GetMapping and @RequestMapping(method=HttpMethod.GET) support HTTP HEAD transparently for request mapping purposes. Controller methods need not change. A response wrapper, applied in the HttpHandler server adapter, ensures a Content-Length header is set to the number of bytes written without actually writing to the response.

默认情况下,HTTP OPTIONS 会通过将 Allow 响应头设置为所有具有匹配 URL 模式的 @RequestMapping 方法中列出的 HTTP 方法列表来进行处理。

By default, HTTP OPTIONS is handled by setting the Allow response header to the list of HTTP methods listed in all @RequestMapping methods with matching URL patterns.

对于没有 HTTP 方法声明的 @RequestMappingAllow 头部会设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS。控制器方法应始终声明所支持的 HTTP 方法(例如,通过使用 HTTP 方法特定的变体 @GetMapping@PostMapping 等)。

For a @RequestMapping without HTTP method declarations, the Allow header is set to GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS. Controller methods should always declare the supported HTTP methods (for example, by using the HTTP method specific variants — @GetMapping, @PostMapping, and others).

您可以将 @RequestMapping 方法显式映射到 HTTP HEAD 和 HTTP OPTIONS,但在通常情况下无需这样做。

You can explicitly map a @RequestMapping method to HTTP HEAD and HTTP OPTIONS, but that is not necessary in the common case.

Custom Annotations

Spring WebFlux 支持使用 composed annotations 进行请求映射。这些是使用 @RequestMapping 进行元注解且合成以重新声明 @RequestMapping 属性的一个子集(或全部)的注解,其具有较窄、更具体的目的。

Spring WebFlux supports the use of composed annotations for request mapping. Those are annotations that are themselves meta-annotated with @RequestMapping and composed to redeclare a subset (or all) of the @RequestMapping attributes with a narrower, more specific purpose.

@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping 是组合注释的示例。提供这些注释是因为从理论上讲,大多数控制器方法都应该映射到特定的 HTTP 方法,而不是使用默认情况下与所有 HTTP 方法匹配的 @RequestMapping。如果您需要有关如何实现组合注释的示例,请查看如何声明这些注释。

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, and @PatchMapping are examples of composed annotations. They are provided, because, arguably, most controller methods should be mapped to a specific HTTP method versus using @RequestMapping, which, by default, matches to all HTTP methods. If you need an example of how to implement a composed annotation, look at how those are declared.

@RequestMapping 不能与在同一元素(类、接口或方法)上声明的其他 @RequestMapping 注释结合使用。如果在同一元素上检测到多个 @RequestMapping 注释,将记录一条警告,并且只会使用第一个映射。这也适用于 @GetMapping@PostMapping 等复合 @RequestMapping 注释。

@RequestMapping cannot be used in conjunction with other @RequestMapping annotations that are declared on the same element (class, interface, or method). If multiple @RequestMapping annotations are detected on the same element, a warning will be logged, and only the first mapping will be used. This also applies to composed @RequestMapping annotations such as @GetMapping, @PostMapping, etc.

Spring WebFlux 还支持具有自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要子类化 RequestMappingHandlerMapping 并覆盖 getCustomMethodCondition 方法,您可以在其中检查自定义属性并返回您自己的 RequestCondition

Spring WebFlux also supports custom request mapping attributes with custom request matching logic. This is a more advanced option that requires sub-classing RequestMappingHandlerMapping and overriding the getCustomMethodCondition method, where you can check the custom attribute and return your own RequestCondition.

Explicit Registrations

您可以以编程方式注册处理程序方法,可用于动态注册或高级案例,例如不同 URL 下相同处理程序的不同实例。以下示例展示了如何执行此操作:

You can programmatically register Handler methods, which can be used for dynamic registrations or for advanced cases, such as different instances of the same handler under different URLs. The following example shows how to do so:

Java
@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 Inject target handlers and the handler mapping for controllers.
2 Prepare the request mapping metadata.
3 Get the handler method.
4 Add the registration.
Kotlin
@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)
	}
}
5 Inject target handlers and the handler mapping for controllers.
6 Prepare the request mapping metadata.
7 Get the handler method.
8 Add the registration.

@HttpExchange

虽然 @HttpExchange 的主要目的是使用生成的代理抽象 HTTP 客户端代码,但是放置此类注释的 HTTP Interface 是一个对于客户端和服务器使用而言呈中立态度的合约。除了简化客户端代码之外,还有部分情况下,一个 HTTP 接口可能是一种便利的方法,让服务器为客户端访问公开其 API。这会导致客户端与服务器耦合度升高,对于公共 API,这往往不是一个好选择,但对于内部 API 来说,它可能正是目标。这在 Spring Cloud 中是一种常用的方法,这也是 @HttpExchange 作为服务器端处理的替代方案在控制器类中受支持的原因。

While the main purpose of @HttpExchange is to abstract HTTP client code with a generated proxy, the HTTP Interface on which such annotations are placed is a contract neutral to client vs server use. In addition to simplifying client code, there are also cases where an HTTP Interface may be a convenient way for servers to expose their API for client access. This leads to increased coupling between client and server and is often not a good choice, especially for public API’s, but may be exactly the goal for an internal API. It is an approach commonly used in Spring Cloud, and it is why @HttpExchange is supported as an alternative to @RequestMapping for server side handling in controller classes.

例如:

For example:

  • 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 and @RequestMapping have differences. @RequestMapping can map to any number of requests by path patterns, HTTP methods, and more, while @HttpExchange declares a single endpoint with a concrete HTTP method, path, and content types.

对于方法参数和返回值,通常,@HttpExchange 支持 @RequestMapping 支持的方法参数的一个子集。值得注意的是,它排除了任何服务器端特定参数类型。有关详细信息,请参见 @HttpExchange@RequestMapping 的列表。

For method parameters and returns values, generally, @HttpExchange supports a subset of the method parameters that @RequestMapping does. Notably, it excludes any server-side specific parameter types. For details, see the list for @HttpExchange and @RequestMapping.