URI 链接
本节描述了 Spring Framework 中用于准备 URI 的各种选项。
UriComponents
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 帮助根据带有变量的 URI 模板构建 URI,如下例所示
-
Java
-
Kotlin
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 | 带有 URI 模板的静态工厂方法。 |
| 2 | 添加或替换 URI 组件。 |
| 3 | 请求对 URI 模板和 URI 变量进行编码。 |
| 4 | 构建一个 UriComponents。 |
| 5 | 展开变量并获取 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 | 带有 URI 模板的静态工厂方法。 |
| 2 | 添加或替换 URI 组件。 |
| 3 | 请求对 URI 模板和 URI 变量进行编码。 |
| 4 | 构建一个 UriComponents。 |
| 5 | 展开变量并获取 URI。 |
上述示例可以合并到一个链中,并使用 buildAndExpand 缩短,如下例所示
-
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(这隐含了编码)进一步缩短它,如下例所示
-
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 模板进一步缩短它,如下例所示
-
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
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 实现了 UriBuilder。你可以通过一个 UriBuilderFactory 创建一个 UriBuilder。UriBuilderFactory 和 UriBuilder 一起提供了一种可插拔的机制,用于根据共享配置(例如基本 URL、编码偏好及其他详细信息)从 URI 模板构建 URI。
你可以使用 UriBuilderFactory 配置 RestTemplate 和 WebClient,以定制 URI 的准备过程。DefaultUriBuilderFactory 是 UriBuilderFactory 的一个默认实现,它在内部使用 UriComponentsBuilder 并暴露共享的配置选项。
下例展示了如何配置 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
-
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,但它不是静态工厂方法,而是一个实际的实例,持有配置和偏好设置,如下例所示
-
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 解析
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 支持两种 URI 解析器类型
-
RFC 解析器——这种解析器类型期望 URI 字符串符合 RFC 3986 语法,并将语法偏差视为非法。
-
WhatWG 解析器——此解析器基于 WhatWG URL Living Standard 中的 URL 解析算法。它提供对各种意外输入的宽松处理。浏览器实现此功能是为了宽松地处理用户输入的 URL。有关更多详细信息,请参阅 URL Living Standard 和 URL 解析测试用例。
默认情况下,RestClient、WebClient 和 RestTemplate 使用 RFC 解析器类型,并期望应用程序提供符合 RFC 语法的 URL 模板。要更改此设置,你可以在任何客户端上定制 UriBuilderFactory。
应用程序和框架可以进一步依赖 UriComponentsBuilder 来解析用户提供的 URL,以便检查并可能验证 URI 组件,例如 scheme、host、port、path 和 query。这些组件可以决定使用 WhatWG 解析器类型来更宽松地处理 URL,并在重定向到输入 URL 或响应中包含该 URL 的情况下,与浏览器解析 URI 的方式保持一致。
URI 编码
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 在两个级别上提供了编码选项
-
UriComponentsBuilder#encode():首先对 URI 模板进行预编码,然后在展开时严格编码 URI 变量。 -
UriComponents#encode():在 URI 变量展开后编码 URI 组件。
这两种选项都将非 ASCII 和非法字符替换为转义八位字节。然而,第一种选项还会替换 URI 变量中出现的具有保留含义的字符。
| 考虑分号“;”,它在路径中是合法的,但具有保留含义。第一种选项在 URI 变量中将“;”替换为“%3B”,但在 URI 模板中不替换。相比之下,第二种选项永远不会替换“;”,因为它在路径中是合法字符。 |
在大多数情况下,第一种选项可能会产生预期的结果,因为它将 URI 变量视为需要完全编码的不透明数据;而第二种选项在 URI 变量有意包含保留字符时非常有用。第二种选项在完全不展开 URI 变量时也很有用,因为它会编码任何偶然看起来像 URI 变量的内容。
以下示例使用第一种选项
-
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(这隐含了编码)来缩短上述示例,如下例所示
-
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 模板进一步缩短它,如下例所示
-
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")
WebClient 和 RestTemplate 通过 UriBuilderFactory 策略在内部展开和编码 URI 模板。这两者都可以通过自定义策略进行配置,如下例所示
-
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 模板。作为工厂,它提供了一个单一的位置来配置编码方法,基于以下编码模式之一
-
TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),对应于前面列表中的第一种选项,用于预编码 URI 模板并在展开时严格编码 URI 变量。 -
VALUES_ONLY:不对 URI 模板进行编码,而是通过UriUtils#encodeUriVariables在将 URI 变量展开到模板之前对其应用严格编码。 -
URI_COMPONENT:使用UriComponents#encode(),对应于前面列表中的第二种选项,用于在 URI 变量展开后编码 URI 组件值。 -
NONE:不应用任何编码。
RestTemplate 出于历史原因和向后兼容性设置为 EncodingMode.URI_COMPONENT。WebClient 依赖于 DefaultUriBuilderFactory 中的默认值,该默认值在 5.0.x 版本中从 EncodingMode.URI_COMPONENT 更改为 5.1 版本中的 EncodingMode.TEMPLATE_AND_VALUES。