Mapping Requests
本部分讨论了带注解控制器的请求映射。
This section discusses request mapping for annotated controllers.
@RequestMapping
你可以使用 @RequestMapping
注解将请求映射到控制器方法。它具有各种属性,可根据 URL、HTTP 方法、请求参数、标头和媒体类型进行匹配。你可以在类级别使用它来表达共享映射,也可以在方法级别使用它来缩小到特定端点映射。
You can use the @RequestMapping
annotation 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 shortcuts 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.
A @RequestMapping
is still needed at the class level to express shared mappings.
|
|
以下示例具有类型和方法级别映射:
The following example has 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
可以使用 URL 模式映射 @RequestMapping
方法。有两种选择:
@RequestMapping
methods can be mapped using URL patterns. There are two alternatives:
-
PathPattern
— a pre-parsed pattern matched against the URL path also pre-parsed asPathContainer
. Designed for web use, this solution deals effectively with encoding and path parameters, and matches efficiently. -
AntPathMatcher
— match String patterns against a String path. This is the original solution also used in Spring configuration to select resources on the classpath, on the filesystem, and other locations. It is less efficient and the String path input is a challenge for dealing effectively with encoding and other issues with URLs.
PathPattern
是适用于 Web 应用程序的推荐解决方案,并且是 Spring WebFlux 中唯一的选择。自版本 5.3 起,它已在 Spring MVC 中启用,且自版本 6.0 起默认启用。请参见 MVC config 了解路径匹配选项的自定义。
PathPattern
is the recommended solution for web applications and it is the only choice in
Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is enabled by
default from version 6.0. See MVC config for
customizations of path matching options.
PathPattern
支持与 AntPathMatcher
相同的模式语法。此外,它还支持捕获模式,例如 {spring}
, for matching 0 or more path segments
at the end of a path. PathPattern
also restricts the use of *
,用于匹配多个路径段,因此它只允许在模式的末尾。当为给定的请求选择最佳匹配模式时,这消除了许多模棱两可的情况。有关完整的模式语法,请参考 PathPattern 和 AntPathMatcher。
PathPattern
supports the same pattern syntax as AntPathMatcher
. In addition, it also
supports the capturing pattern, e.g. {spring}
, for matching 0 or more path segments
at the end of a path. PathPattern
also restricts the use of *
for matching multiple
path segments such that it’s only allowed at the end of a pattern. This eliminates many
cases of ambiguity when choosing the best matching pattern for a given request.
For full pattern syntax please refer to
PathPattern and
AntPathMatcher.
一些示例模式:
Some example patterns:
-
"/resources/ima?e.png"
- match one character in a path segment -
"/resources/*.png"
- match zero or more characters in a path segment -
"/resources/**"
- match multiple path segments -
"/projects/{project}/versions"
- match a path segment and capture it as a variable -
"/projects/{project:[a-z]}/versions"+
- match and capture a variable with a regex
可以通过 @PathVariable
访问捕获的 URI 变量。例如:
Captured URI variables can be accessed with @PathVariable
. For example:
-
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
-
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
等),您可以注册对任何其他数据类型提供支持。请参见 Type Conversion 和 DataBinder
。
URI variables are automatically converted to the appropriate type, or 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
编译器标志编译的,那么你可以省略该详细信息。
You can explicitly name URI variables (for example, @PathVariable("customId")
), but you can
leave that detail out if the names are the same and your code is compiled with the -parameters
compiler flag.
语法 {varName:regex}
用具有 {varName:regex}
语法的正则表达式声明一个 URI 变量。例如,给定 URL "/spring-web-3.0.5.jar"
,以下方法会提取名称、版本和文件扩展名:
The syntax {varName:regex}
declares a URI variable with a regular expression that has
syntax of {varName:regex}
. For example, given URL "/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 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。
URI path patterns can also have embedded ${…}
placeholders that are resolved on startup
by using PropertySourcesPlaceholderConfigurer
against local, system, environment, and
other property sources. You can use this, for example, to parameterize a base URL based on
some external configuration.
Pattern Comparison
当多个模式匹配 URL 时,必须选择最佳匹配。这会通过以下方式之一完成,具体取决于是否启用使用已解析的 PathPattern
:
When multiple patterns match a URL, the best match must be selected. This is done with
one of the following depending on whether use of parsed PathPattern
is enabled for use or not:
两种方法都有助于排序模式,将更具体的模式排在顶部。如果 URI 变量数(算作 1 个)、单个通配符(算作 1 个)和双重通配符(算作 2 个)较少,则模式越具体。给定相同分数,选择较长的模式。在分数和长度相同的情况下,选择具有比通配符更多的 URI 变量的模式。
Both help to sort patterns with more specific ones on top. A pattern is more specific if it has a lower count of URI variables (counted as 1), single wildcards (counted as 1), and double wildcards (counted as 2). Given an equal score, the longer pattern is chosen. Given the same score and length, the pattern with more URI variables than wildcards is chosen.
默认映射模式 (/
) 被排除在评分之外,并且始终排在最后。此外,前缀模式(如 /public/
)被认为不如没有双重通配符的其他模式具体。
The default mapping pattern (/
) is excluded from scoring and always
sorted last. Also, prefix patterns (such as /public/
) are considered less
specific than other pattern that do not have double wildcards.
有关完整详细信息,请按照上述链接访问模式比较器。
For the full details, follow the above links to the pattern Comparators.
Suffix Match
从 5.3 开始,默认情况下,Spring MVC 不再执行 .
后缀模式匹配,其中映射到 /person
的控制器也隐式映射到 /person.
。因此,路径扩展不再用于解释响应的请求内容类型,例如 /person.pdf
、/person.xml
等。
Starting in 5.3, by default Spring MVC no longer performs .
suffix pattern
matching where a controller mapped to /person
is also implicitly mapped to
/person.
. As a consequence path extensions are no longer used to interpret
the requested content type for the response — for example, /person.pdf
, /person.xml
,
and so on.
当浏览器用于发送难以一致解释的 Accept
头时,需要以这种方式使用文件扩展名。目前,这不再是必需的,并且首选使用 Accept
头。
Using file extensions in this way was necessary when browsers used to send Accept
headers
that were hard to interpret consistently. At present, that is no longer a necessity and
using the Accept
header should be the preferred choice.
随着时间的推移,文件扩展名的使用已被证明在各种方式上存在问题。当与 URI 变量、路径参数和 URI 编码一起使用时,可能会导致歧义。基于 URL 的授权和安全性(有关更多详细信息,请参阅下一部分)的推理也变得更加困难。
Over time, the use of file name extensions has proven problematic in a variety of ways. It can cause ambiguity when overlain with the use of URI variables, path parameters, and URI encoding. Reasoning about URL-based authorization and security (see next section for more details) also becomes more difficult.
要在 5.3 之前的版本中完全禁用路径扩展名的使用,请设置以下内容:
To completely disable the use of path extensions in versions prior to 5.3, set the following:
-
useSuffixPatternMatching(false)
, see PathMatchConfigurer -
favorPathExtension(false)
, see ContentNegotiationConfigurer
除了通过 "Accept"
头部以外,还有一种方法可以请求内容类型,例如在浏览器中键入 URL 时仍然有用。路径扩展的安全替代方法是使用查询参数策略。如果您必须使用文件扩展名,请考虑通过 mediaTypes
的 ContentNegotiationConfigurer 属性将它们限制为显式注册扩展名的列表。
Having a way to request content types other than through the "Accept"
header can still
be useful, e.g. when typing a URL in a browser. A safe alternative to path extensions is
to use the query parameter strategy. If you must use file extensions, consider restricting
them to a list of explicitly registered extensions through the mediaTypes
property of
ContentNegotiationConfigurer.
Suffix Match and RFD
反射文件下载 (RFD) 攻击类似于 XSS,因为它依赖于请求输入(例如,查询参数和 URI 变量)反映在响应中。但是,RFD 攻击不是将 JavaScript 插入到 HTML 中,而是依赖于浏览器切换以执行下载,并在以后双击时将响应视为可执行脚本。
A reflected file download (RFD) attack is similar to XSS in that it relies on request input (for example, a query parameter and a URI variable) being reflected in the response. However, instead of inserting JavaScript into HTML, an RFD attack relies on the browser switching to perform a download and treating the response as an executable script when double-clicked later.
在 Spring MVC 中,@ResponseBody
和 ResponseEntity
方法存在风险,因为它们可以呈现不同的内容类型,客户端可以通过 URL 路径扩展名请求这些内容类型。禁用后缀模式匹配和使用路径扩展名进行内容协商可以降低风险,但不足以防止 RFD 攻击。
In Spring MVC, @ResponseBody
and ResponseEntity
methods are at risk, because
they can render different content types, which clients can request through URL path extensions.
Disabling suffix pattern matching and using path extensions for content negotiation
lower the risk but are not sufficient to prevent RFD attacks.
为了防止 RFD 攻击,Spring MVC 在呈现响应主体之前会添加一个 Content-Disposition:inline;filename=f.txt
头以建议一个固定且安全的下载文件。仅当 URL 路径包含既不被允许作为安全,也未明确为内容协商而注册的文件扩展名时,才会执行此操作。但是,当 URL 直接键入浏览器时,它可能会产生副作用。
To prevent RFD attacks, prior to rendering the response body, Spring MVC adds a
Content-Disposition:inline;filename=f.txt
header to suggest a fixed and safe download
file. This is done only if the URL path contains a file extension that is neither
allowed as safe nor explicitly registered for content negotiation. However, it can
potentially have side effects when URLs are typed directly into a browser.
许多常见的路径扩展被默认允许为安全。具有 custom`HttpMessageConverter` 实现的应用程序可显式注册文件扩展,以进行内容协商,以避免为这些扩展添加 Content-Disposition
标头。请参见 Content Types。
Many common path extensions are allowed as safe by default. Applications with custom
HttpMessageConverter
implementations can explicitly register file extensions for content
negotiation to avoid having a Content-Disposition
header added for those extensions.
See Content Types.
请参见 CVE-2015-5211 了解与 RFD 相关的其他建议。
See CVE-2015-5211 for additional recommendations related to RFD.
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
-
@PostMapping(path = "/pets", consumes = "application/json") (1) public void addPet(@RequestBody Pet pet) { // ... }
1 | Using a consumes attribute to narrow the mapping by the content type.
|
2 | Using a consumes attribute to narrow the mapping by the content type. |
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.
|
|
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
-
@GetMapping(path = "/pets/{petId}", produces = "application/json") (1) @ResponseBody public Pet getPet(@PathVariable String petId) { // ... }
1 | Using a produces attribute to narrow the mapping by the content type.
|
2 | Using a produces attribute to narrow the mapping by the content type. |
媒体类型可以指定一个字符集。支持否定表达式 - 例如 !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 extends the class-level declaration.
|
|
Parameters, headers
你可以根据请求参数条件缩小请求映射范围。你可以测试请求参数(myParam
)的存在、不存在(!myParam
)或特定值(myParam=myValue
)。以下示例展示了如何测试特定值:
You can narrow request mappings based on request parameter conditions. You can test for the
presence of a request parameter (myParam
), for the absence of one (!myParam
), or for a
specific value (myParam=myValue
). The following example shows how to test for a specific value:
- Java
-
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1) public void findPet(@PathVariable String petId) { // ... }
1 | Testing whether myParam equals myValue .
|
2 | Testing whether 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 | Testing whether myHeader equals myValue .
|
2 | Testing whether myHeader equals myValue . |
HTTP HEAD, OPTIONS
@GetMapping
(和 @RequestMapping(method=HttpMethod.GET)
)为请求映射透明地支持 HTTP HEAD。控制器方法无需更改。jakarta.servlet.http.HttpServlet 中应用的响应包装器确保将 Content-Length
头设置为已写入字节数(而不实际写入响应)。
@GetMapping
(and @RequestMapping(method=HttpMethod.GET)
) support HTTP HEAD
transparently for request mapping. Controller methods do not need to change.
A response wrapper, applied in jakarta.servlet.http.HttpServlet
, 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 that have matching URL patterns.
对于不带有 HTTP 方法声明的 @RequestMapping
,Allow
头设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
。Controller 方法应始终声明受支持的 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 the @RequestMapping
method to HTTP HEAD and HTTP OPTIONS, but that
is not necessary in the common case.
Custom Annotations
Spring MVC 支持将 composed annotations 用于请求映射。这些是本身已通过 @RequestMapping
进行元注释并由更窄、更具体目的的 @RequestMapping
属性的子集(或全部)重新声明的注释。
Spring MVC 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.
|
|
Spring MVC 还支持带有自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要对 RequestMappingHandlerMapping
进行子类化并覆盖 getCustomMethodCondition
方法,在该方法中你可以检查自定义属性并返回你自己的 RequestCondition
。
Spring MVC also supports custom request-mapping attributes with custom request-matching
logic. This is a more advanced option that requires subclassing
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 you can use for dynamic registrations or for advanced cases, such as different instances of the same handler under different URLs. The following example registers a handler method:
- 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 the target handler and the handler mapping for controllers. |
2 | Prepare the request mapping meta data. |
3 | Get the handler method. |
4 | Add the registration.
|
5 | Inject the target handler and the handler mapping for controllers. |
6 | Prepare the request mapping meta data. |
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.