(#mvc-uri-building)1.5. URI Links 1.5. URI 链接

预计阅读时间: 17 分钟

This section describes various options available in the Spring Framework to work with URI’s.
本节描述了 Spring 框架中可用于处理 URI 的各种选项。

(#web-uricomponents)1.5.1. UriComponents

Spring MVC and Spring WebFlux
Spring MVC 和 Spring WebFlux

UriComponentsBuilder helps to build URI’s from URI templates with variables, as the following example shows:
UriComponentsBuilder 帮助从带有变量的 URI 模板中构建 URI,如下例所示:

UriComponents uriComponents = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") (1) .queryParam("q", "{q}") (2) .encode() (3) .build(); (4) URI uri = uriComponents.expand("Westin", "123").toUri(); (5)

1

Static factory method with a URI template.
静态工厂方法与 URI 模板。

2

Add or replace URI components.
添加或替换 URI 组件。

3

Request to have the URI template and URI variables encoded.
请求对 URI 模板和 URI 变量进行编码。

4

Build a UriComponents. 构建一个 UriComponents

5

Expand variables and obtain the URI.
展开变量并获取 URI

val uriComponents = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") (1) .queryParam("q", "{q}") (2) .encode() (3) .build() (4) val uri = uriComponents.expand("Westin", "123").toUri() (5)

1

Static factory method with a URI template.

2

Add or replace URI components.

3

Request to have the URI template and URI variables encoded.

4

Build a UriComponents.

5

Expand variables and obtain the URI.

The preceding example can be consolidated into one chain and shortened with buildAndExpand, as the following example shows:
前一个示例可以合并为一个链,并用 buildAndExpand 缩短,如下例所示:

URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .encode() .buildAndExpand("Westin", "123") .toUri();
val uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .encode() .buildAndExpand("Westin", "123") .toUri()

You can shorten it further by going directly to a URI (which implies encoding), as the following example shows:
您可以通过直接访问 URI(这表示编码)来进一步缩短它,如下例所示:

URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123");
val uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123")

You can shorten it further still with a full URI template, as the following example shows:
您可以使用完整的 URI 模板进一步缩短,如下例所示:

URI uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}?q={q}") .build("Westin", "123");
val uri = UriComponentsBuilder .fromUriString("https://example.com/hotels/{hotel}?q={q}") .build("Westin", "123")

(#web-uribuilder)1.5.2. UriBuilder

Spring MVC and Spring WebFlux
Spring MVC 和 Spring WebFlux

UriComponentsBuilder implements UriBuilder. You can create a UriBuilder, in turn, with a UriBuilderFactory. Together, UriBuilderFactory and UriBuilder provide a pluggable mechanism to build URIs from URI templates, based on shared configuration, such as a base URL, encoding preferences, and other details.
UriComponentsBuilder 实现 UriBuilder 。您可以使用 UriBuilderFactory 创建一个 UriBuilder ,从而 UriBuilderFactoryUriBuilder 提供了一种基于共享配置(如基础 URL、编码偏好和其他详细信息)的可插拔机制,用于从 URI 模板构建 URI。

You can configure RestTemplate and WebClient with a UriBuilderFactory to customize the preparation of URIs. DefaultUriBuilderFactory is a default implementation of UriBuilderFactory that uses UriComponentsBuilder internally and exposes shared configuration options.
您可以使用 RestTemplateWebClient 配置 UriBuilderFactory 以自定义 URI 的准备。 DefaultUriBuilderFactoryUriBuilderFactory 的默认实现,它内部使用 UriComponentsBuilder 并公开共享配置选项。

The following example shows how to configure a RestTemplate:
以下示例展示了如何配置一个 RestTemplate :

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; String baseUrl = "https://example.org"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES); RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory);
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode val baseUrl = "https://example.org" val factory = DefaultUriBuilderFactory(baseUrl) factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES val restTemplate = RestTemplate() restTemplate.uriTemplateHandler = factory

The following example configures a WebClient:
以下示例配置了一个 WebClient

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; String baseUrl = "https://example.org"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES); WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode val baseUrl = "https://example.org" val factory = DefaultUriBuilderFactory(baseUrl) factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES val client = WebClient.builder().uriBuilderFactory(factory).build()

In addition, you can also use DefaultUriBuilderFactory directly. It is similar to using UriComponentsBuilder but, instead of static factory methods, it is an actual instance that holds configuration and preferences, as the following example shows:
此外,您还可以直接使用 DefaultUriBuilderFactory 。它与使用 UriComponentsBuilder 类似,但不同之处在于,它不是一个静态工厂方法,而是一个实际实例,它包含配置和首选项,如下例所示:

String baseUrl = "https://example.com"; DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl); URI uri = uriBuilderFactory.uriString("/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123");
val baseUrl = "https://example.com" val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl) val uri = uriBuilderFactory.uriString("/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123")

(#web-uri-encoding)1.5.3. URI Encoding 1.5.3. URI 编码

Spring MVC and Spring WebFlux
Spring MVC 和 Spring WebFlux

UriComponentsBuilder exposes encoding options at two levels:
UriComponentsBuilder 在两个级别上公开编码选项:

  • UriComponentsBuilder#encode(): Pre-encodes the URI template first and then strictly encodes URI variables when expanded.
    UriComponentsBuilder#encode():首先预编码 URI 模板,然后在展开时严格编码 URI 变量。

  • UriComponents#encode(): Encodes URI components after URI variables are expanded.
    UriComponents#encode():在展开 URI 变量后对 URI 组件进行编码。

Both options replace non-ASCII and illegal characters with escaped octets. However, the first option also replaces characters with reserved meaning that appear in URI variables.
两个选项都将非 ASCII 码和非法字符替换为转义八进制数。然而,第一个选项还将出现在 URI 变量中的具有保留意义的字符替换掉。

Consider ";", which is legal in a path but has reserved meaning. The first option replaces ";" with "%3B" in URI variables but not in the URI template. By contrast, the second option never replaces ";", since it is a legal character in a path.
考虑分号";",它在路径中是合法的,但具有保留意义。第一种选项在 URI 变量中将分号替换为"%3B",但不在 URI 模板中替换。相比之下,第二种选项从不替换分号,因为它在路径中是一个合法字符。

For most cases, the first option is likely to give the expected result, because it treats URI variables as opaque data to be fully encoded, while the second option is useful if URI variables do intentionally contain reserved characters.
对于大多数情况,第一个选项更有可能给出预期的结果,因为它将 URI 变量视为不可见的数据进行完全编码,而第二个选项在 URI 变量故意包含保留字符时很有用。
The second option is also useful when not expanding URI variables at all since that will also encode anything that incidentally looks like a URI variable.
第二种选项在完全不扩展 URI 变量时也有用,因为那样也会对任何偶然看起来像 URI 变量的内容进行编码。

The following example uses the first option:
以下示例使用第一个选项:

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .encode() .buildAndExpand("New York", "foo+bar") .toUri(); // Result is "/hotel%20list/New%20York?q=foo%2Bbar"
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .encode() .buildAndExpand("New York", "foo+bar") .toUri() // Result is "/hotel%20list/New%20York?q=foo%2Bbar"

You can shorten the preceding example by going directly to the URI (which implies encoding), as the following example shows:
您可以直接通过 URI(这意味着编码)来缩短前面的示例,如下例所示:

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .build("New York", "foo+bar");
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}") .queryParam("q", "{q}") .build("New York", "foo+bar")

You can shorten it further still with a full URI template, as the following example shows:
您可以使用完整的 URI 模板进一步缩短,如下例所示:

URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}") .build("New York", "foo+bar");
val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}") .build("New York", "foo+bar")

The WebClient and the RestTemplate expand and encode URI templates internally through the UriBuilderFactory strategy. Both can be configured with a custom strategy, as the following example shows:
WebClientRestTemplate 通过 UriBuilderFactory 策略内部扩展和编码 URI 模板。两者都可以配置为自定义策略,如下例所示:

String baseUrl = "https://example.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl) factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES); // Customize the RestTemplate.. RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory); // Customize the WebClient.. WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
val baseUrl = "https://example.com" val factory = DefaultUriBuilderFactory(baseUrl).apply { encodingMode = EncodingMode.TEMPLATE_AND_VALUES } // Customize the RestTemplate.. val restTemplate = RestTemplate().apply { uriTemplateHandler = factory } // Customize the WebClient.. val client = WebClient.builder().uriBuilderFactory(factory).build()

The DefaultUriBuilderFactory implementation uses UriComponentsBuilder internally to expand and encode URI templates. As a factory, it provides a single place to configure the approach to encoding, based on one of the below encoding modes:
DefaultUriBuilderFactory 实现内部使用 UriComponentsBuilder 来扩展和编码 URI 模板。作为一个工厂,它提供了一个配置编码方法的单一位置,基于以下编码模式之一:

  • TEMPLATE_AND_VALUES: Uses UriComponentsBuilder#encode(), corresponding to the first option in the earlier list, to pre-encode the URI template and strictly encode URI variables when expanded.
    TEMPLATE_AND_VALUES :使用 UriComponentsBuilder#encode() ,对应于之前列表中的第一个选项,以预编码 URI 模板并在展开时严格编码 URI 变量。

  • VALUES_ONLY: Does not encode the URI template and, instead, applies strict encoding to URI variables through UriUtils#encodeUriVariables prior to expanding them into the template.
    VALUES_ONLY : 不对 URI 模板进行编码,而是在将其扩展到模板之前,通过 UriUtils#encodeUriVariables 对 URI 变量进行严格的编码。

  • URI_COMPONENT: Uses UriComponents#encode(), corresponding to the second option in the earlier list, to encode URI component value after URI variables are expanded.
    URI_COMPONENT : 使用 UriComponents#encode() ,对应于之前列表中的第二个选项,在展开 URI 变量后对 URI 组件值进行编码。

  • NONE: No encoding is applied.
    NONE : 没有应用编码。

The RestTemplate is set to EncodingMode.URI_COMPONENT for historic reasons and for backwards compatibility. The WebClient relies on the default value in DefaultUriBuilderFactory, which was changed from EncodingMode.URI_COMPONENT in 5.0.x to EncodingMode.TEMPLATE_AND_VALUES in 5.1.
RestTemplate 设置为 EncodingMode.URI_COMPONENT 是出于历史原因和向后兼容性的考虑。 WebClient 依赖于 DefaultUriBuilderFactory 中的默认值,该值在 5.0.x 中从 EncodingMode.URI_COMPONENT 更改为 5.1 中的 EncodingMode.TEMPLATE_AND_VALUES

(#mvc-servleturicomponentsbuilder)1.5.4. Relative Servlet Requests

1.5.4. 相对 Servlet 请求

You can use ServletUriComponentsBuilder to create URIs relative to the current request, as the following example shows:
您可以使用 ServletUriComponentsBuilder 创建相对于当前请求的 URI,如下例所示:

HttpServletRequest request = ... // Re-uses scheme, host, port, path, and query string... URI uri = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}") .build("123");
val request: HttpServletRequest = ... // Re-uses scheme, host, port, path, and query string... val uri = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}") .build("123")

You can create URIs relative to the context path, as the following example shows:
您可以根据上下文路径创建相对 URI,如下例所示:

HttpServletRequest request = ... // Re-uses scheme, host, port, and context path... URI uri = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts") .build() .toUri();
val request: HttpServletRequest = ... // Re-uses scheme, host, port, and context path... val uri = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts") .build() .toUri()

You can create URIs relative to a Servlet (for example, /main/*), as the following example shows:
您可以创建相对于 Servlet 的 URI(例如, /main/* ),如下例所示:

HttpServletRequest request = ... // Re-uses scheme, host, port, context path, and Servlet mapping prefix... URI uri = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts") .build() .toUri();
val request: HttpServletRequest = ... // Re-uses scheme, host, port, context path, and Servlet mapping prefix... val uri = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts") .build() .toUri()

As of 5.1, ServletUriComponentsBuilder ignores information from the Forwarded and X-Forwarded-* headers, which specify the client-originated address. Consider using the ForwardedHeaderFilter to extract and use or to discard such headers.
截至 5.1 版本, ServletUriComponentsBuilder 忽略了来自 ForwardedX-Forwarded-* 头部的信息,这些头部指定了客户端源地址。请考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃此类头部。

1.5.5. 控制器链接

Spring MVC provides a mechanism to prepare links to controller methods. For example, the following MVC controller allows for link creation:
Spring MVC 提供了一种准备控制器方法链接的机制。例如,以下 MVC 控制器允许创建链接:

@Controller @RequestMapping("/hotels/{hotel}") public class BookingController { @GetMapping("/bookings/{booking}") public ModelAndView getBooking(@PathVariable Long booking) { // ... } }
@Controller @RequestMapping("/hotels/{hotel}") class BookingController { @GetMapping("/bookings/{booking}") fun getBooking(@PathVariable booking: Long): ModelAndView { // ... } }

You can prepare a link by referring to the method by name, as the following example shows:
您可以通过按名称引用方法来准备一个链接,如下例所示:

UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
val uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42) val uri = uriComponents.encode().toUri()

In the preceding example, we provide actual method argument values (in this case, the long value: 21) to be used as a path variable and inserted into the URL. Furthermore, we provide the value, 42, to fill in any remaining URI variables, such as the hotel variable inherited from the type-level request mapping. If the method had more arguments, we could supply null for arguments not needed for the URL. In general, only @PathVariable and @RequestParam arguments are relevant for constructing the URL.
在前面示例中,我们提供了实际方法参数值(在这种情况下,长整型值: 21 ),用作路径变量并插入到 URL 中。此外,我们还提供了值, 42 ,以填充任何剩余的 URI 变量,例如从类型级别请求映射继承的 hotel 变量。如果方法有更多参数,我们可以为不需要用于 URL 的参数提供 null。一般来说,只有 @PathVariable@RequestParam 参数与构造 URL 相关。

There are additional ways to use MvcUriComponentsBuilder. For example, you can use a technique akin to mock testing through proxies to avoid referring to the controller method by name, as the following example shows (the example assumes static import of MvcUriComponentsBuilder.on):
存在其他使用 MvcUriComponentsBuilder 的方法。例如,您可以通过类似于通过代理进行模拟测试的技术来避免通过名称引用控制器方法,如下例所示(该示例假设静态导入 MvcUriComponentsBuilder.on ):

UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
val uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42) val uri = uriComponents.encode().toUri()

Controller method signatures are limited in their design when they are supposed to be usable for link creation with fromMethodCall. Aside from needing a proper parameter signature, there is a technical limitation on the return type ( namely, generating a runtime proxy for link builder invocations), so the return type must not be final. In particular, the common String return type for view names does not work here. You should use ModelAndView or even plain Object (with a String return value) instead.
控制器方法签名在设计上有限,当它们应该与 fromMethodCall 一起用于创建链接时。除了需要一个合适的参数签名外,还存在对返回类型的限制(即生成用于链接构建器调用的运行时代理),因此返回类型不能是 final 。特别是,对于视图名称的常见 String 返回类型在这里不适用。你应该使用 ModelAndView 或者甚至纯 Object (带有 String 返回值)代替。

The earlier examples use static methods in MvcUriComponentsBuilder. Internally, they rely on ServletUriComponentsBuilder to prepare a base URL from the scheme, host, port, context path, and servlet path of the current request. This works well in most cases. However, sometimes, it can be insufficient.
早期示例使用 MvcUriComponentsBuilder 中的静态方法。内部,它们依赖于 ServletUriComponentsBuilder 从当前请求的方案、主机、端口、上下文路径和 servlet 路径准备基本 URL。这在大多数情况下都很好用。然而,有时这可能不够。
For example, you may be outside the context of a request (such as a batch process that prepares links) or perhaps you need to insert a path prefix (such as a locale prefix that was removed from the request path and needs to be re-inserted into links).
例如,您可能处于请求上下文之外(例如,准备链接的批处理过程)或者可能需要插入路径前缀(例如,从请求路径中移除的本地化前缀,需要重新插入到链接中)。

For such cases, you can use the static fromXxx overloaded methods that accept a UriComponentsBuilder to use a base URL. Alternatively, you can create an instance of MvcUriComponentsBuilder with a base URL and then use the instance-based withXxx methods. For example, the following listing uses withMethodCall:
对于此类情况,您可以使用接受一个 UriComponentsBuilder 的静态 fromXxx 重载方法来使用基本 URL。或者,您可以使用带有基本 URL 的 MvcUriComponentsBuilder 实例,然后使用基于实例的 withXxx 方法。例如,以下列表使用 withMethodCall

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en"); MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base); builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en") val builder = MvcUriComponentsBuilder.relativeTo(base) builder.withMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42) val uri = uriComponents.encode().toUri()

As of 5.1, MvcUriComponentsBuilder ignores information from the Forwarded and X-Forwarded-* headers, which specify the client-originated address. Consider using the ForwardedHeaderFilter to extract and use or to discard such headers.
截至 5.1 版本, MvcUriComponentsBuilder 忽略了来自 ForwardedX-Forwarded-* 头部的信息,这些头部指定了客户端原始地址。请考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃此类头部。

1.5.6. 视图中的链接

In views such as Thymeleaf, FreeMarker, or JSP, you can build links to annotated controllers by referring to the implicitly or explicitly assigned name for each request mapping.
在 Thymeleaf、FreeMarker 或 JSP 等视图中,您可以通过引用每个请求映射的隐式或显式分配的名称来构建指向注解控制器的链接。

Consider the following example:
考虑以下示例:

@RequestMapping("/people/{id}/addresses") public class PersonAddressController { @RequestMapping("/{country}") public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... } }
@RequestMapping("/people/{id}/addresses") class PersonAddressController { @RequestMapping("/{country}") fun getAddress(@PathVariable country: String): HttpEntity<PersonAddress> { ... } }

Given the preceding controller, you can prepare a link from a JSP, as follows:
给定前面的控制器,您可以从 JSP 中准备一个链接,如下所示:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> ... <a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

The preceding example relies on the mvcUrl function declared in the Spring tag library (that is, META-INF/spring.tld), but it is easy to define your own function or prepare a similar one for other templating technologies.
前一个示例依赖于在 Spring 标签库中声明的 mvcUrl 函数(即 META-INF/spring.tld),但定义自己的函数或为其他模板技术准备一个类似的函数很容易。

Here is how this works. On startup, every @RequestMapping is assigned a default name through HandlerMethodMappingNamingStrategy, whose default implementation uses the capital letters of the class and the method name (for example, the getThing method in ThingController becomes "TC#getThing"). If there is a name clash, you can use @RequestMapping(name="..") to assign an explicit name or implement your own HandlerMethodMappingNamingStrategy.
这里是如何工作的。在启动时,每个 @RequestMapping 都会通过 HandlerMethodMappingNamingStrategy 分配一个默认名称,其默认实现使用类的首字母和方法名称(例如, ThingController 中的 getThing 方法变为"TC#getThing" )。如果有名称冲突,您可以使用 @RequestMapping(name="..") 分配一个显式名称或实现自己的 HandlerMethodMappingNamingStrategy