Request Mapping

你可以使用 @RequestMapping 注解来 映射请求到控制器 方法。它有各种属性,可以通过 URL、HTTP 方法、请求参数、请求头和媒体类型进行匹配。你可以在 类的层面上使用它来表达共享映射,也可以在方法的层面上使用它来缩小到一个特定的端点映射。

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

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

捷径是 自定义注解,因为可以说,大多数控制器方法应该被映射到一个特定的 HTTP 方法,而不是使用 @RequestMapping,默认情况下,它匹配所有的 HTTP 方法。在类的层面上仍然需要一个 @RequestMapping来表达共享映射。

下面的例子有类型和方法层面的映射:

  1. @RestController
  2. @RequestMapping("/persons")
  3. class PersonController {
  4. @GetMapping("/{id}")
  5. public Person getPerson(@PathVariable Long id) {
  6. // ...
  7. }
  8. @PostMapping
  9. @ResponseStatus(HttpStatus.CREATED)
  10. public void add(@RequestBody Person person) {
  11. // ...
  12. }
  13. }

URI patterns / URI 模式

@RequestMapping 方法可以使用 URL 模式进行映射。有两种选择:

  • PathPattern:一个与 URL 路径相匹配的预解析模式,也被预解析为 PathContainer。这个方案是为网络使用而设计的,它有效地处理了编码和路径参数,并有效地进行了匹配。

  • AntPathMatcher : 针对字符串路径匹配字符串模式。这是最初的解决方案,也用于 Spring 配置中,以选择 classpath 上的资源、文件系统上的资源和其他位置。它的效率较低,而且字符串路径输入对于有效处理 URL 的编码和其他问题是一个挑战。

PathPattern 是 Web 应用的推荐解决方案,也是 Spring WebFlux 的唯一选择。在 5.3 版本之前,AntPathMatcher 是 Spring MVC 中的唯一选择,并继续作为默认选项。但是 PathPattern 可以在 MVC 配置 中启用。

PathPattern 支持与 AntPathMatcher 相同的模式语法。此外,它还支持捕获模式,例如 {*spring},用于匹配路径末端的 0 个或多个路径段。PathPattern 还限制了 **在匹配多个路径段时的使用,即只允许在模式的末端使用。这就消除了在为给定请求选择最佳匹配模式时出现的许多模糊情况。关于完整的模式语法,请参考 PathPatternAntPathMatcher

一些例子模式:

  • "/resources/ima?e.png" : 匹配路径段中的一个字符
  • "/resources/*.png":在一个路径段中匹配零个或多个字符
  • "/resources/**" : 匹配多个路径段
  • "/projects/{project}/versions" : 匹配一个路径段并将其作为一个变量捕获
  • "/projects/{project:[a-z]+}/versions": 匹配一个路径段并捕获一个变量

捕获的 URI 变量可以用 @PathVariable访问。比如说:

  1. @GetMapping("/owners/{ownerId}/pets/{petId}")
  2. public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
  3. // ...
  4. }

你可以在类和方法层面上声明 URI 变量,如下例所示:

  1. @Controller
  2. @RequestMapping("/owners/{ownerId}")
  3. public class OwnerController {
  4. @GetMapping("/pets/{petId}")
  5. public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
  6. // ...
  7. }
  8. }

URI 变量会自动转换为适当的类型,否则会引发 TypeMismatchException。默认支持简单的类型(int、long、Date 等),你可以注册对任何其他数据类型的支持。参见 类型转换DataBinder

你可以明确地命名 URI 变量(例如,@PathVariable("customId")),但如果名称相同,并且你的代码是用调试信息或 Java 8 上的 -parameters编译器标志来编译的,你可以不考虑这个细节(JDK8 使用 -parameters 编译可保留方法上的参数原始名称)。

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

  1. @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
  2. public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
  3. // ...
  4. }

URI 路径模式也可以有嵌入的 ${...}占位符,在启动时通过使用 PropertyPlaceHolderConfigurer 针对本地、系统、环境和其他属性源进行解析。例如,你可以用它来根据一些外部配置对基本 URL 进行参数化。

Pattern Comparison / 模式比较

当多个模式匹配一个 URL 时,必须选择最佳匹配。这是根据是否启用使用解析的 PathPattern 来完成的,取决于是否启用使用

  • [PathPattern.SPECIFICITY_COMPARATOR](https://docs.spring.io/spring-framework/docs/5.3.15/javadoc-api/org/springframework/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR)
  • [AntPathMatcher.getPatternComparator(String path)](https://docs.spring.io/spring-framework/docs/5.3.15/javadoc-api/org/springframework/util/AntPathMatcher.html#getPatternComparator-java.lang.String-)

两者都有助于对模式进行排序,更具体的模式在上面。如果一个模式的 URI 变量(计为 1)、单通配符(计为 1)和双通配符(计为 2)的数量较少,那么它的特异性就较低。在得分相同的情况下,选择较长的模式。在分数和长度相同的情况下,选择 URI 变量多于通配符的模式。

默认的映射模式(/**)被排除在评分之外,并且总是被排在最后。另外,前缀模式(如 /public/**)被认为比其他没有双通配符的模式更不具体。

关于完整的细节,请按照上述链接查看模式比较器。

Suffix Match / 后缀匹配

从 5.3 版本开始,默认情况下 Spring MVC 不再执行 .*后缀模式匹配,即映射到 /person的控制器也隐含地映射到 /person.*。因此,路径扩展名不再被用来解释响应所要求的内容类型,例如,/person.pdf/person.xml,等等。

当浏览器曾经发送难以一致解释的 Accept 头时,以这种方式使用文件扩展名是必要的。目前,这已不再是必要的,使用 Accept头应该是首选。

随着时间的推移,文件名扩展名的使用已被证明在很多方面存在问题。当它与 URI 变量、路径参数和 URI 编码的使用叠加在一起时,会引起歧义。推理基于 URL 的授权和安全(更多细节见下一节)也变得更加困难。

要完全禁止在 5.3 之前的版本中使用路径扩展,请设置如下:

除了通过 Accept 头之外,有一种方法可以请求内容类型,仍然是有用的,例如在浏览器中输入一个 URL。路径扩展的一个安全选择是使用查询参数策略。如果你必须使用文件扩展,考虑通过 ContentNegotiationConfigurer 的 mediaTypes 属性将它们限制在明确注册的扩展列表中。

Suffix Match and RFD / 后缀匹配和 RFD

反射式文件下载(RFD)攻击与 XSS 类似,它依赖于请求输入(例如,查询参数和 URI 变量)在响应中被反射。然而,RFD 攻击不是在 HTML 中插入 JavaScript,而是依靠浏览器切换到执行下载,并在以后双击时将响应视为可执行脚本。

在 Spring MVC 中,@ResponseBodyResponseEntity方法存在风险,因为它们可以呈现不同的内容类型,客户可以通过 URL 路径扩展请求这些内容。禁用后缀模式匹配和使用路径扩展进行内容协商降低了风险,但并不足以防止 RFD 攻击。

为了防止 RFD 攻击,在渲染响应主体之前,Spring MVC 添加了 Content-Disposition:inline;filename=f.txt头,以建议一个固定的安全下载文件。只有当 URL 路径包含一个既不允许安全也没有明确注册的内容协商的文件扩展时,才会这样做。然而,当 URL 被直接输入到浏览器中时,它可能会产生副作用。

许多常见的路径扩展名在默认情况下被允许为安全的。具有自定义 HttpMessageConverter 实现的应用程序可以为内容协商明确地注册文件扩展名,以避免为这些扩展名添加 Content-Disposition 头。见 内容类型

参见 CVE-2015-5211,了解与 RFD 相关的其他建议。

Consumable Media Types / 可消费的媒体类型

你可以根据 请求的内容类型 来缩小请求映射的范围,如下例所示:

  1. @PostMapping(path = "/pets", consumes = "application/json")
  2. public void addPet(@RequestBody Pet pet) {
  3. // ...
  4. }

使用 consumes 属性,通过 content type 缩小映射范围。

consumes 属性还支持否定表达式,例如,!text/plain意味着除 text/plain 以外的任何内容类型。

你可以在类层次上声明一个共享的 consumes 属性。然而,与大多数其它请求映射属性不同的是,当在类层使用时,方法层的 consumeres 属性会覆盖而不是扩展类层的声明。

:::info MediaType 为常用的媒体类型提供常量,例如 APPLICATION_JSON_VALUE 和 APPLICATION_XML_VALUE。 :::

Producible Media Types / 可产生的媒体类型

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

  1. @GetMapping(path = "/pets/{petId}", produces = "application/json")
  2. @ResponseBody
  3. public Pet getPet(@PathVariable String petId) {
  4. // ...
  5. }

使用 produces 属性,通过 content type 缩小映射范围。

和 Consumable Media Types 一样,同样可以在类级别上声明

Parameters, headers

你可以根据 请求参数 的条件来缩小请求映射的范围。你可以测试 是否存在一个请求参数(myParam),是否没有一个(!myParam),或者 一个特定的值(myParam=myValue)。下面的例子显示了如何测试一个特定的值:

  1. @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
  2. public void findPet(@PathVariable String petId) {
  3. // ...
  4. }

测试 myParam 是否等于 myValue。

你也可以对请求头条件使用同样的方法,如下例所示:

  1. @GetMapping(path = "/pets", headers = "myHeader=myValue")
  2. public void findPet(@PathVariable String petId) {
  3. // ...
  4. }

测试 myHeader 是否等于 myValue。

:::info 你可以用 headers 条件匹配 Content-Type 和 Accept,但最好使用 consumeresproduction。 :::

HTTP HEAD, OPTIONS

@GetMapping(和 @RequestMapping(method=HttpMethod.GET))支持 HTTP HEAD 透明地进行请求映射。控制器方法不需要改变。在javax.servlet.http.HttpServlet中应用的响应包装器,确保 Content-Length头被设置为写入的字节数(不需要实际写入到响应中)。

@GetMapping(和 @RequestMapping(method=HttpMethod.GET))被隐含地映射到并支持 HTTP HEAD。一个 HTTP HEAD 请求的处理就像HTTP GET 一样,除了不写正文,而是计算字节数并设置 Content-Length 头。

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

对于没有 HTTP 方法声明的 @RequestMapping,Allow 头被设置为 GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS。控制器方法应该始终声明支持的 HTTP 方法(例如,通过使用 HTTP 方法的特定变体。@GetMapping, @PostMapping, 和其他)。

你可以明确地将 @RequestMapping方法映射到 HTTP HEAD 和 HTTP OPTIONS,但在普通情况下没有必要这样做。

Custom Annotations / 自定义注解

Spring MVC 支持在请求映射中使用 组合注解。这些注解本身是用 @RequestMapping进行元注解的,并组成了一个子集(或全部)@RequestMapping属性的重新声明,其目的更加狭窄,更加具体。

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, 和 @PatchMapping 是组和注解的例子。之所以提供这些注解,是因为大多数控制器方法应该被映射到特定的 HTTP 方法上,而不是使用 @RequestMapping,默认情况下,它与所有的 HTTP 方法相匹配。如果你需要一个组合注解的例子,看看这些注解是如何声明的。

Spring MVC 还支持带有自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要子类化 RequestMappingHandlerMapping 并重写 getCustomMethodCondition方法,你可以检查自定义属性并返回你自己的 RequestCondition。

显式注册

你可以以 编程方式注册处理程序方法,你可以将其用于 动态注册 或用于 高级情况,例如在不同的 URL 下同一处理程序的不同实例。下面的例子注册了一个处理程序方法。

  1. @Configuration
  2. public class MyConfig {
  3. @Autowired
  4. // 注入 控制器映射 和 目标处理程序
  5. public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler)
  6. throws NoSuchMethodException {
  7. // 准备好请求映射的元数据。
  8. RequestMappingInfo info = RequestMappingInfo
  9. .paths("/user/{id}").methods(RequestMethod.GET).build();
  10. // 获取处理程序方法。 所以这个 UserHandler 就是被 IOC 容器管理的对象
  11. Method method = UserHandler.class.getMethod("getUser", Long.class);
  12. // 添加注册
  13. mapping.registerMapping(info, handler, method);
  14. }
  15. }

下面是 UserHandler 简单的一个例子

  1. package cn.mrcode.study.springdocsread.web;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.web.bind.annotation.ResponseBody;
  4. /**
  5. * @author mrcode
  6. */
  7. @Component
  8. public class UserHandler {
  9. @ResponseBody
  10. public String getUser(Long userId) {
  11. return "模拟用户:" + userId;
  12. }
  13. }

然后可以通过访问路径如: [http://localhost:8080/user/123](http://localhost:8080/user/123) 访问到这个方法