请求映射
本节讨论了注解控制器的请求映射。
@RequestMapping
可以使用 @RequestMapping
元注解将请求映射到控制器方法。它有各种属性来匹配 URL、HTTP 方法、请求参数、请求头和媒体类型。
您可以在类级别使用它来表达共享映射,或者在方法级别使用它来缩小到特定的端点映射。
还有特定于 HTTP 方法的变体 @RequestMapping
:
-
@GET
-
@POST
-
@PUT
-
@DELETE
-
@PATCH
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
这些快捷方式是 自定义注解,
之所以提供它们,是因为可以说,大多数控制器方法应该映射到特定的 HTTP 方法,而不是使用默认情况下匹配所有 HTTP
方法的 @RequestMapping
。在类级别仍然需要 @RequestMapping
来表达共享映射。
@RequestMapping 不能与在同一元素(类、接口或方法)上声明的其他 @RequestMapping
注解一起使用。如果在同一个元素上检测到多个 @RequestMapping 注解,将记录一条警告,并且只使用第一个映射。
这也适用于诸如 @GET 、@POST 等组合的 @RequestMapping 注解。
|
以下示例具有类型和方法级别的映射:
@RestController
@RequestMapping("/persons")
class PersonController {
@GET("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@POST
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI 匹配
@RequestMapping
方法可以使用 URL patterns 进行映射。有两种选择:
-
PathPattern
— 一个预先解析的模式,与预先解析为PathContainer
的 URL 路径匹配。 这种解决方案专为 Web 使用而设计,有效处理编码和路径参数,并有效匹配。 -
AntPathMatcher
— 将字符串模式与字符串路径匹配。这是原始解决方案,也用框架 配置中选择类路径、文件系统和其他位置的资源。它效率较低,且字符串路径输入在有效处理编码和其他 URL 问题方面是一个挑战。
PathPattern
是 Web 应用程序的推荐解决方案。从版本 5.0 开始在 Web MVC 是唯一的选择。
请参阅 MVC 配置 了解路径匹配选项的自定义。
PathPattern
支持与 AntPathMatcher
相同的模式语法。此外,它还支持捕获模式,例如 {*today}
,
用于匹配路径末尾的 0 个或多个路径段。PathPattern
还限制了 **
的使用,仅允许在模式的末尾匹配多个路径段。
这消除了在选择给定请求的最佳匹配模式时的许多歧义情况。有关完整模式语法,
请参阅 PathPattern
和 AntPathMatcher。
一些示例 pattern:
-
"/resources/ima?e.png"
— 在路径段中匹配一个字符 -
"/resources/*.png"
— 在路径段中匹配零个或多个字符 -
"/resources/**"
— 匹配多个路径段 -
"/projects/{project}/versions"
— 匹配一个路径段并将其捕获为变量 -
"/projects/{project:[a-z]}/versions"+
— 匹配并捕获具有正则表达式的变量
捕获的 URI 变量可以使用 @PathVariable
访问。例如:
@GET("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
你可以在类或者方法上声明 URI 变量,例如:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GET("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URI 变量会自动转换为适当的类型,或者抛出 TypeMismatchException
。
简单类型(如 int
、long
、Date
等)默认受到支持,您可以注册对任何其他数据类型的支持。
请参阅 类型转换
和 DataBinder
。
您可以显式命名 URI 变量(例如,@PathVariable("customId")
),但如果名称相同,并且您的代码使用了
-parameters
编译器标志进行编译,则可以省略此细节。
语法 {varName:regex}
声明了一个具有正则表达式的 URI 变量,该正则表达式的语法为 {varName:regex}
。
例如,给定URL /today-web-4.0.jar
,以下方法提取了名称、版本和文件扩展名:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
URI 路径 patterns 也可以包含嵌入式 ${…}
占位符,这些占位符在启动时通过使用
PropertySourcesPlaceholderConfigurer
与本地、系统、环境和其他 PropertySource 结合解析。
例如,您可以使用此功能,基于某些外部配置,参数化一个 baseURL。
Pattern 比较
当多个 Pattern 匹配一个 URL 时,必须选择最佳匹配。这可以通过以下方式之一完成,具体的 PathPattern
的使用:
PathPattern.SPECIFICITY_COMPARATOR
这有助于将更最佳的 Pattern 排在前面。如果一个 Pattern 具有更少的 URI 变量(每个计数为1)、 单个通配符(每个计数为1)和双通配符(计数为2),则该 Pattern 更靠前。在得分相等的情况下,选择更长的 Pattern。 在得分和长度相同的情况下,选择 URI 变量多于通配符的模式。
默认映射 Pattern (/**
) 被排除在评分之外,并且始终排序在最后。
此外,前缀 Pattern(例如 /public/**
)被认为比其他没有双通配符的 Pattern 更不具体。
有关完整详细信息,请参见上述链接中的模式比较器。
可处理的媒体类型
您可以根据请求的 Content-Type
来缩小请求映射的范围,如下例所示:
@POST(path = "/pets", consumes = "application/json") (1)
public void addPet(@RequestBody Pet pet) {
// ...
}
1 | 使用 consumes 属性通过内容类型来缩小映射范围。 |
consumes
属性还支持否定表达式—例如,!text/plain
表示除了 text/plain
之外的任何内容类型。
您可以在类级别声明共享的 consumes
属性。然而,与大多数其他请求映射属性不同,当在类级别使用时,
方法级别的 consumes
属性会覆盖而不是扩展类级别的声明。
MediaType 提供了常用媒体类型的常量,如 APPLICATION_JSON_VALUE 和 APPLICATION_XML_VALUE 。
|
返回的媒体类型
您可以根据 Accept
请求头和控制器方法产生的内容类型列表来缩小请求映射的范围,如下例所示:
@GET(path = "/pets/{petId}", produces = "application/json") (1)
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
1 | 使用 produces 属性通过内容类型来缩小映射范围。 |
媒体类型可以指定字符集。支持否定表达式——例如,!text/plain
表示除了 "text/plain" 之外的任何内容类型。
您可以在类级别声明共享的 produces
属性。然而,与大多数其他请求映射属性不同,当在类级别使用时,
方法级别的 produces
属性会覆盖而不是扩展类级别的声明。
MediaType 提供了常用媒体类型的常量,如 APPLICATION_JSON_VALUE 和 APPLICATION_XML_VALUE 。
|
请求参数和请求头
您可以根据请求参数条件来缩小请求映射的范围。您可以测试请求参数的存在(myParam
)、
不存在(!myParam
)或特定值(myParam=myValue
)。以下示例展示了如何测试特定值:
@GET(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
// ...
}
1 | 检查 myParam 是否等于 myValue 。 |
您也可以使用相同的方法来测试请求头条件,如下例所示:
@GET(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
// ...
}
1 | 检查 myHeader 是否等于 myValue . |
HTTP HEAD, OPTIONS
@GET
(以及 @RequestMapping(method=HttpMethod.GET)
)支持 HTTP HEAD 的请求映射。
控制器方法无需更改。在响应包装器确保将 Content-Length
头部设置为写入的字节数(而实际上不写入响应)。
默认情况下,HTTP OPTIONS 通过将 Allow
响应头部设置为所有具有匹配 URL 模式的 @RequestMapping
方法中列出的 HTTP 方法列表来处理。
对于没有 HTTP 方法声明的 @RequestMapping
,Allow
头部被设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
。
控制器方法应始终声明支持的 HTTP 方法(例如,通过使用特定于 HTTP 方法的变体:@GET
、@POST
等)。
您可以显式地将 @RequestMapping
方法映射到 HTTP HEAD 和 HTTP OPTIONS,但在常见情况下这不是必需的。
自定义注解
Web MVC 支持使用 组合注解
进行请求映射。这些注解本身使用 @RequestMapping
进行元注解,并组合起来重新声明 @RequestMapping
的子集(或全部)属性,以实现更狭窄、更具体的目的。
@GET
、@POST
、@PUT
、@DELETE
和 @PatchMapping
是组合注解的示例。
之所以提供这些注解,是因为可以说,大多数控制器方法应该映射到特定的 HTTP 方法,而不是使用默认匹配所有 HTTP 方法的
@RequestMapping
。如果您需要一个如何实现组合注解的示例,请查看这些注解是如何声明的。
注意:@RequestMapping
不能与在同一元素(类、接口或方法)上声明的其他 @RequestMapping
注解一起使用。
如果在同一个元素上检测到多个 @RequestMapping
注解,将记录一条警告,并且只使用第一个映射。
这也适用于如 @GET
、@POST
等组合的 @RequestMapping
注解。
Web MVC 还支持使用自定义请求映射属性和自定义请求匹配逻辑。这是一个更高级的选项,需要通过扩展
RequestMappingHandlerMapping
并覆盖 getCustomCondition
方法来实现,
在那里您可以检查自定义属性并返回您自己的 RequestCondition
。
显式注册
可以以编程方式注册处理器方法,这可以用于动态注册或高级用例,例如在不同 URL 下使用相同的处理器的不同实例。 以下示例展示了如何注册一个处理器方法:
@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 | 添加注册。 |
@HttpExchange
虽然 @HttpExchange
的主要目的是通过生成的代理抽象 HTTP 客户端代码,但这样的注解所放置的
HTTP 接口 是一个与客户端与服务器使用无关的契约。
除了简化客户端代码外,还有一些情况下,HTTP 接口可能是服务器方便地暴露其 API 供客户端访问的方式。
这导致客户端和服务器之间的耦合增加,通常不是一个好的选择,特别是对于公共 API,但可能正是内部 API 的目标。
这种方法也是为什么 @HttpExchange
被支持作为 @RequestMapping
的替代品,用于控制器类中的服务器端处理。
例如:
@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
和 @RequestMapping
在功能上有所区别:
- @RequestMapping
可以通过路径模式、HTTP 方法等映射到任意数量的请求。
- @HttpExchange
声明了一个具有具体 HTTP 方法、路径和内容类型的单一端点。
对于方法参数和返回值:
- 通常,@HttpExchange
支持 @RequestMapping
支持的方法参数的一个子集。
- 特别地,它不包括任何特定于服务器端的参数类型。
有关详细信息,请参阅: - @HttpExchange 方法参数的列表。 - @RequestMapping 方法参数的列表。