映射请求

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

@RequestMapping

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

还有HTTP方法特定的快捷方式变体:@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping

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

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

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

  • 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模式

您可以使用通配符和通配符来映射请求:

模式 描述 示例

?

匹配一个字符

"/pages/t?st.html" 匹配 "/pages/test.html""/pages/t3st.html"

*

匹配路径段中的零个或多个字符

"/resources/*.png" 匹配 "/resources/file.png"

"/projects/*/versions" 匹配 "/projects/spring/versions" 但不匹配 "/projects/spring/boot/versions"

**

匹配路径的零个或多个路径段,直到路径结束

"/resources/**" 匹配 "/resources/file.png""/resources/images/file.png"

"/resources/**/file.png" 是无效的,因为**只允许出现在路径的末尾。

{name}

匹配路径段并将其捕获为名为"name"的变量

"/projects/{project}/versions" 匹配 "/projects/spring/versions" 并捕获 project=spring

{name:[a-z]+}

将正则表达式"[a-z]+"作为名为"name"的路径变量进行匹配

"/projects/{project:[a-z]+}/versions" 匹配 "/projects/spring/versions" 但不匹配 "/projects/spring1/versions"

{*path}

匹配路径的零个或多个路径段,直到路径结束,并将其捕获为名为"path"的变量

"/resources/{*file}" 匹配 "/resources/images/file.png" 并捕获 file=/images/file.png

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

  • 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变量,如下例所示:

  • Java

  • Kotlin

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

	@GetMapping("/pets/{petId}") (2)
	public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
		// ...
	}
}
1 类级别URI映射。
2 方法级别URI映射。
@Controller
@RequestMapping("/owners/{ownerId}") (1)
class OwnerController {

	@GetMapping("/pets/{petId}") (2)
	fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
		// ...
	}
}
1 类级别URI映射。
2 方法级别URI映射。

URI变量会自动转换为适当的类型,或者会引发TypeMismatchException。简单类型(如intlongDate等)默认支持,您还可以注册对任何其他数据类型的支持。请参阅类型转换DataBinder

URI变量可以明确命名(例如@PathVariable("customId")),但如果名称相同并且使用-parameters编译器标志编译代码,则可以省略该细节。

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

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

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

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

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

模式比较

当多个模式匹配一个URL时,它们必须进行比较以找到最佳匹配项。这是通过PathPattern.SPECIFICITY_COMPARATOR来实现的,它寻找更具体的模式。

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

捕获所有模式(例如,**{*varName})被排除在得分之外,并始终排在最后。如果两个模式都是捕获所有模式,则选择较长的模式。

可消费的媒体类型

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

  • 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之外的任何内容类型。

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

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

可生产的媒体类型

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

  • 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之外的任何内容类型。

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

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

参数和头部

您可以根据查询参数条件缩小请求映射范围。您可以测试查询参数的存在(myParam),不存在(!myParam),或具有特定值(myParam=myValue)。以下示例测试具有值的参数:

  • Java

  • Kotlin

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
	// ...
}
1 检查myParam是否等于myValue
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
1 检查myParam是否等于myValue

您也可以使用相同的方式进行请求头条件,如下例所示:

  • Java

  • Kotlin

@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
	// ...
}
1 检查myHeader是否等于myValue
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
1 检查myHeader是否等于myValue

HTTP HEAD, OPTIONS

@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.

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.

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).

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 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, 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 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 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

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

  • Kotlin

@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.
@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)
	}
}
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.

@HttpExchange

虽然@HttpExchange的主要目的是使用生成的代理来抽象HTTP客户端代码,但放置这些注解的HTTP接口是一个与客户端与服务器使用中立的契约。除了简化客户端代码外,还有一些情况下,HTTP接口可能是服务器为客户端访问其API提供的一种便捷方式。这会增加客户端和服务器之间的耦合,并且通常不是一个好选择,特别是对于公共API,但对于内部API可能正是目标所在。这是Spring Cloud中常用的一种方法,也是为什么@HttpExchange被支持作为控制器类中处理服务器端的@RequestMapping的替代方法。

例如:

  • 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支持@RequestMapping支持的方法参数的子集。特别地,它排除了任何特定于服务器端的参数类型。有关详细信息,请参阅@HttpExchange@RequestMapping的列表。