请求映射

本节讨论了注解控制器的请求映射。

@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 还限制了 ** 的使用,仅允许在模式的末尾匹配多个路径段。 这消除了在选择给定请求的最佳匹配模式时的许多歧义情况。有关完整模式语法, 请参阅 PathPatternAntPathMatcher

一些示例 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。 简单类型(如 intlongDate 等)默认受到支持,您可以注册对任何其他数据类型的支持。 请参阅 类型转换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_VALUEAPPLICATION_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_VALUEAPPLICATION_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.
您可以使用头条件匹配 Content-TypeAccept,但最好使用 consumesproduces 代替。

HTTP HEAD, OPTIONS

@GET(以及 @RequestMapping(method=HttpMethod.GET))支持 HTTP HEAD 的请求映射。 控制器方法无需更改。在响应包装器确保将 Content-Length 头部设置为写入的字节数(而实际上不写入响应)。

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

对于没有 HTTP 方法声明的 @RequestMappingAllow 头部被设置为 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 方法参数的列表。