URI链接
本节描述了Spring框架中可用于准备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
带有URI模板的静态工厂方法。
添加或替换URI组件。
- \ [#3] 请求对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
带有URI模板的静态工厂方法。
添加或替换URI组件。
- \ [#3] 请求对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。
你可以使用 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类似,但不同的是,DefaultUriBuilderFactory不是一个静态工厂方法,而是一个实际的实例,该实例包含了配置和偏好设置,如下例所示:
- 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生活标准中的URL解析算法。它能对各种意外的输入情况进行较为宽松的处理。浏览器实现这种解析器是为了能够较为宽容地处理用户输入的URL。更多详情,请参阅URL生活标准和URL解析测试用例。
默认情况下,RestClient、WebClient 和 RestTemplate 使用 RFC 解析器类型,并要求应用程序提供符合 RFC 语法的 URL 模板。要更改这一点,您可以在这些客户端中的任意一个上自定义 UriBuilderFactory。
应用程序和框架可能还会根据自身需求依赖UriComponentsBuilder来解析用户提供的URL,以便检查并可能验证URI的各个组成部分,如协议(scheme)、主机(host)、端口(port)、路径(path)和查询参数(query)。这些组件可以选择使用WhatWG解析器类型,以更宽松的方式来处理URL,并与浏览器解析URL的方式保持一致。这在需要重定向到输入URL时,或者当URL作为对浏览器的响应的一部分时尤为有用。
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 模板,而是在将 URI 变量展开到模板中之前,通过UriUtils#encodeUriVariables对这些变量进行严格的编码。 -
URI_COMPONENT:使用UriComponents#encode()(对应于前面列表中的第二个选项),在 URI 变量被展开之后对 URI 组件的值进行编码。 -
NONE:不应用任何编码。
出于历史原因和向后兼容性的考虑,RestTemplate被设置为EncodingMode URI COMPONENT。而WebClient则依赖于DefaultUriBuilderFactory的默认值,该默认值在5.0.x版本中是EncodingMode.URI_COMPONENT,但在5.1版本中被修改为EncodingMode.TEMPLATE_ANDVALUES。