URI Links

此部分介绍了 Spring Framework 中用于处理 URI 的各种可用选项。

This section describes various options available in the Spring Framework to work with URI’s.

UriComponents

  1. 通过静态工厂方法从 URI 模板创建 URI 组件。

  2. 添加或替换 URI 组件,例如查询参数或路径变量。

  3. 对 URI 模板和 URI 变量进行编码,以确保跨网络安全传输。

  4. 构建 UriComponents 对象,其中包含 URI 模板和变量。

  5. 展开变量并获取 URI,生成最终的请求 URI。 Spring MVC and Spring WebFlux

UriComponentsBuilder 有助于通过具有变量的 URI 模板来构建 URI,如下例所示:

UriComponentsBuilder helps to build URI’s from URI templates with variables, as the following example shows:

Java
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.
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.
Kotlin
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)
6 Static factory method with a URI template.
7 Add or replace URI components.
8 Request to have the URI template and URI variables encoded.
9 Build a UriComponents.
10 Expand variables and obtain the URI.

前面的示例可以合并到一个链中并使用 buildAndExpand 缩短,如下例所示:

The preceding example can be consolidated into one chain and shortened with buildAndExpand, as the following example shows:

  • Java

  • Kotlin

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

您可以更进一步缩短它,直接转到 URI(这意味着编码),如下例所示:

You can shorten it further by going directly to a URI (which implies encoding), as the following example shows:

  • Java

  • Kotlin

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

您还可以使用完整的 URI 模板进一步缩短它,如下例所示:

You can shorten it further still with a full URI template, as the following example shows:

  • Java

  • Kotlin

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

UriBuilder

[role="small"][.small]Spring MVC 和 Spring WebFlux

[role="small"][.small]Spring MVC and Spring WebFlux

<<`UriComponentsBuilder`,web-uricomponents>> 实现 UriBuilder。您可以创建一个 UriBuilder,反过来,可以使用 UriBuilderFactory 创建一个 UriBuilderUriBuilderFactoryUriBuilder 一起提供了一种可插入机制,用于基于共享配置(例如基本 URL、编码首选项和其他详细信息)从 URI 模板构建 URI。

<<`UriComponentsBuilder`,web-uricomponents>> 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.

您可以使用 UriBuilderFactory 配置 RestTemplateWebClient 以自定义 URI 的准备。DefaultUriBuilderFactoryUriBuilderFactory 的默认实现,它在内部使用 UriComponentsBuilder 并公开共享配置选项。

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.

以下示例显示如何配置 RestTemplate

The following example shows how to configure a RestTemplate:

  • Java

  • Kotlin

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

以下示例配置 WebClient

The following example configures a WebClient:

  • Java

  • Kotlin

// 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()

此外,您还可以直接使用 DefaultUriBuilderFactory。它类似于使用 UriComponentsBuilder,但不是静态工厂方法,而是一个实际的实例,包含配置和首选项,如下例所示:

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:

  • Java

  • Kotlin

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

URI Encoding

[role="small"][.small]Spring MVC 和 Spring WebFlux

[role="small"][.small]Spring MVC and Spring WebFlux

UriComponentsBuilder 在两个级别上公开编码选项:

UriComponentsBuilder exposes encoding options at two levels:

这两个选项都将非 ASCII 和非法字符替换为转义的八位字节序列。但是,第一个选项还将出现在 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.

考虑“;”这个分号在路径中是合法的,但在 URI 模板中不合法,保留着含义。第一个选项用 URI 变量中的“;”替换为“%3B”,但不会将 URI 模板中的“;”替换为“%3B”。相比之下,第二个选项永远不会替换“;”,因为它是路径中的一个合法字符。

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 变量作为要完全编码的不透明数据进行处理,而第二个选项在 URI 变量有意包含保留字符时很有用。第二个选项在根本未扩展 URI 变量时也很有用,因为这也会对偶发 trông giống như một biến 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. 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.

以下示例使用第一个选项:

The following example uses the first option:

  • Java

  • Kotlin

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"

您可以通过直接转到 URI(这意味着编码)来缩短前面的示例,如下例所示:

You can shorten the preceding example by going directly to the URI (which implies encoding), as the following example shows:

  • Java

  • Kotlin

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

您还可以使用完整的 URI 模板进一步缩短它,如下例所示:

You can shorten it further still with a full URI template, as the following example shows:

  • Java

  • Kotlin

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

WebClientRestTemplate 通过 UriBuilderFactory 策略在内部展开和编码 URI 模板。两个都可以使用自定义策略进行配置,如下例所示:

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:

  • Java

  • Kotlin

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

DefaultUriBuilderFactory 实现内部使用 UriComponentsBuilder 来展开和编码 URI 模板。作为工厂,它提供了一个单一的位置来配置编码方法,基于以下编码模式之一:

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:

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

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

  • URI_COMPONENT: Uses UriComponents#encode(), corresponding to the second option in the earlier list, to encode URI component value after URI variables are expanded.

  • NONE: No encoding is applied.

RestTemplate 设置为 EncodingMode.URI_COMPONENT 是出于历史原因和向后兼容性的考虑。WebClient 依赖于 DefaultUriBuilderFactory 中的默认值,该值已从 5.0.x 中 的 EncodingMode.URI_COMPONENT 更改为 5.1 中的 EncodingMode.TEMPLATE_AND_VALUES

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.

Relative Servlet Requests

你可以使用 ServletUriComponentsBuilder 创建相对于当前请求的 URI,如下例所示:

You can use ServletUriComponentsBuilder to create URIs relative to the current request, as the following example shows:

  • Java

  • Kotlin

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

你可以创建相对于上下文路径的 URI,如下例所示:

You can create URIs relative to the context path, as the following example shows:

  • Java

  • Kotlin

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

你可以创建相对于某个 Servlet(例如,/main/*)的 URI,如下例所示:

You can create URIs relative to a Servlet (for example, /main/*), as the following example shows:

  • Java

  • Kotlin

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

从 5.1 开始,ServletUriComponentsBuilder 忽略 ForwardedX-Forwarded-* 标头的信息,这些标头指定客户端源地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃此类标头。

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.

Spring MVC 提供一种准备连接器到控制器方法的机制。例如,下面的 MVC 控制允许连接创建:

Spring MVC provides a mechanism to prepare links to controller methods. For example, the following MVC controller allows for link creation:

  • Java

  • Kotlin

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

  • Java

  • Kotlin

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

在前面的示例中,我们提供了用作路径变量并插入 URL 中的实际方法参数值(在本例中是长整型值:21)。此外,我们提供数值 42 来补充任何剩余的 URI 变量,例如从类型级别请求映射继承的 hotel 变量。如果方法有更多参数,则我们可以为不需要的 URL 参数提供 null 值。通常,只有 @PathVariable@RequestParam 参数与构造 URL 有关。

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.

还有其他使用 MvcUriComponentsBuilder 的方法。例如,您可以使用类似于通过代理进行模拟测试的技术,以避免按名称引用控制器方法,如下面的示例所示(该示例假设静态导入了 MvcUriComponentsBuilder.on):

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

  • Java

  • Kotlin

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

当控制器方法签名用于通过 fromMethodCall 创建链接时,其设计受到限制。除了需要适当的参数签名之外,对返回类型还有技术限制(即,为链接生成器调用生成运行时代理),所以返回类型不能是 final。特别是,这里不起作用的是视图名称的 String 通用返回类型。你应当使用 ModelAndView 或者甚至使用普通的 Object(带有 String 返回值)来代替。

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.

前面的示例使用 MvcUriComponentsBuilder 中的静态方法。在内部,它们依赖于 ServletUriComponentsBuilder 来根据当前请求的协议、主机、端口、上下文路径和 servlet 路径准备一个基础 URL。这在大多数情况下都能正常工作。但是,有时可能不够用。例如,您可能不在请求的上下文中(例如准备链接的批处理进程),或者您可能需要插入路径前缀(例如从请求路径中移除的区域设置前缀,需要重新插入链接中)。

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

对于此类情况,您可以使用接受 UriComponentsBuilder 的静态 fromXxx 重载方法来使用基础 URL。或者,您可以使用基础 URL 创建 MvcUriComponentsBuilder 的实例,然后使用基于实例的 withXxx 方法。例如,下面的清单使用了 withMethodCall

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:

  • Java

  • Kotlin

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

从 5.1 开始,MvcUriComponentsBuilder 忽略 ForwardedX-Forwarded-* 标头的信息,这些标头指定客户端源地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃此类标头。

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.

在 Thymeleaf、FreeMarker 或 JSP 等视图中,您可以通过引用每个请求映射的隐式或显式分配名称来构建指向带注释控制器的链接。

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.

请考虑以下示例:

Consider the following example:

  • Java

  • Kotlin

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

给定了前面的控制器,您可以按以下方式从 JSP 准备链接:

Given the preceding controller, you can prepare a link from a JSP, as follows:

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

前面的示例依赖于在 Spring 标签库(即 META-INF/spring.tld)中声明的 mvcUrl 函数,但是,定义您自己的函数或为其他模板技术准备类似的函数很容易。

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.

它就是这样工作的。在启动时,每个 @RequestMapping 都通过 HandlerMethodMappingNamingStrategy 分配一个默认名称,其默认实现使用类和方法名称首字母(例如,ThingController 中的 getThing 方法变为“TC#getThing”)。如果有名称冲突,您可以使用 @RequestMapping(name="..") 分配显式名称或实现您自己的 HandlerMethodMappingNamingStrategy

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.