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.

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

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

以下示例具有类型和方法级别映射:

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 as PathContainer. 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 *,用于匹配多个路径段,因此它只允许在模式的末尾。当为给定的请求选择最佳匹配模式时,这消除了许多模棱两可的情况。有关完整的模式语法,请参考 PathPatternAntPathMatcher

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。默认情况下支持简单类型(intlongDate 等),您可以注册对任何其他数据类型提供支持。请参见 Type ConversionDataBinder

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:

除了通过 "Accept" 头部以外,还有一种方法可以请求内容类型,例如在浏览器中键入 URL 时仍然有用。路径扩展的安全替代方法是使用查询参数策略。如果您必须使用文件扩展名,请考虑通过 mediaTypesContentNegotiationConfigurer 属性将它们限制为显式注册扩展名的列表。

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 中,@ResponseBodyResponseEntity 方法存在风险,因为它们可以呈现不同的内容类型,客户端可以通过 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.
Kotlin
@PostMapping("/pets", consumes = ["application/json"]) (1)
fun addPet(@RequestBody pet: Pet) {
	// ...
}
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.

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

MediaType provides constants for commonly used media types, such as APPLICATION_JSON_VALUE and APPLICATION_XML_VALUE.

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.
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"]) (1)
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
	// ...
}
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.

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

MediaType provides constants for commonly used media types, such as APPLICATION_JSON_VALUE and APPLICATION_XML_VALUE.

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.
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
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.
Kotlin
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
	// ...
}
2 Testing whether myHeader equals myValue.

您可以使用标头条件匹配 Content-TypeAccept,但最好使用 consumesproduces

You can match Content-Type and Accept with the headers condition, but it is better to use consumes and produces instead.

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 方法声明的 @RequestMappingAllow 头设置为 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.

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

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