URI Links
此部分介绍了 Spring Framework 中用于处理 URI 的各种可用选项。
UriComponents
-
通过静态工厂方法从 URI 模板创建 URI 组件。
-
添加或替换 URI 组件,例如查询参数或路径变量。
-
对 URI 模板和 URI 变量进行编码,以确保跨网络安全传输。
-
构建 UriComponents 对象,其中包含 URI 模板和变量。
-
展开变量并获取 URI,生成最终的请求 URI。 Spring MVC and Spring WebFlux
UriComponentsBuilder
有助于通过具有变量的 URI 模板来构建 URI,如下例所示:
- 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 | 带有 URI 模板的静态工厂方法。 |
2 | 添加或替换 URI 组件。 |
3 | 请求对 URI 模板和 URI 变量进行编码。 |
4 | Build a UriComponents . |
5 | 展开变量并获取 URI 。
|
6 | 带有 URI 模板的静态工厂方法。 |
7 | 添加或替换 URI 组件。 |
8 | 请求对 URI 模板和 URI 变量进行编码。 |
9 | Build a UriComponents . |
10 | 展开变量并获取 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
[role="small"][.small]Spring MVC 和 Spring WebFlux
<<`UriComponentsBuilder`,web-uricomponents>> 实现 UriBuilder
。您可以创建一个 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 Encoding
[role="small"][.small]Spring MVC 和 Spring WebFlux
UriComponentsBuilder
在两个级别上公开编码选项:
-
UriComponentsBuilder#encode(): 先对 URI 模板进行预编码,然后在展开时严格对 URI 变量进行编码。
-
UriComponents#encode(): 对 URI 组件进行编码 after URI 变量被展开。
这两个选项都将非 ASCII 和非法字符替换为转义的八位字节序列。但是,第一个选项还将出现在 URI 变量中的保留含义字符替换为转义字符。
考虑“;”这个分号在路径中是合法的,但在 URI 模板中不合法,保留着含义。第一个选项用 URI 变量中的“;”替换为“%3B”,但不会将 URI 模板中的“;”替换为“%3B”。相比之下,第二个选项永远不会替换“;”,因为它是路径中的一个合法字符。 |
对于大多数情况,第一个选项可能会提供预期的结果,因为它将 URI 变量作为要完全编码的不透明数据进行处理,而第二个选项在 URI 变量有意包含保留字符时很有用。第二个选项在根本未扩展 URI 变量时也很有用,因为这也会对偶发 trông giống như một biến 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 组件值进行编码 after URI 变量被展开。 -
NONE
: 不应用任何编码。
将 RestTemplate
设置为 EncodingMode.URI_COMPONENT
是出于历史原因和向后兼容性的考虑。WebClient
依赖于 DefaultUriBuilderFactory
中的默认值,该值已从 5.0.x 中 的 EncodingMode.URI_COMPONENT
更改为 5.1 中的 EncodingMode.TEMPLATE_AND_VALUES
。
Relative Servlet Requests
你可以使用 ServletUriComponentsBuilder
创建相对于当前请求的 URI,如下例所示:
-
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,如下例所示:
-
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,如下例所示:
-
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 开始, |
Links to Controllers
Spring MVC 提供一种准备连接器到控制器方法的机制。例如,下面的 MVC 控制允许连接创建:
-
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 {
// ...
}
}
您可以通过按名称引用方法来准备链接,如下面的示例所示:
-
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 有关。
还有其他使用 MvcUriComponentsBuilder
的方法。例如,您可以使用类似于通过代理进行模拟测试的技术,以避免按名称引用控制器方法,如下面的示例所示(该示例假设静态导入了 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()
当控制器方法签名用于通过 |
前面的示例使用 MvcUriComponentsBuilder
中的静态方法。在内部,它们依赖于 ServletUriComponentsBuilder
来根据当前请求的协议、主机、端口、上下文路径和 servlet 路径准备一个基础 URL。这在大多数情况下都能正常工作。但是,有时可能不够用。例如,您可能不在请求的上下文中(例如准备链接的批处理进程),或者您可能需要插入路径前缀(例如从请求路径中移除的区域设置前缀,需要重新插入链接中)。
对于此类情况,您可以使用接受 UriComponentsBuilder
的静态 fromXxx
重载方法来使用基础 URL。或者,您可以使用基础 URL 创建 MvcUriComponentsBuilder
的实例,然后使用基于实例的 withXxx
方法。例如,下面的清单使用了 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 开始, |
Links in Views
在 Thymeleaf、FreeMarker 或 JSP 等视图中,您可以通过引用每个请求映射的隐式或显式分配名称来构建指向带注释控制器的链接。
请考虑以下示例:
-
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 准备链接:
<%@ 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
函数,但是,定义您自己的函数或为其他模板技术准备类似的函数很容易。
它就是这样工作的。在启动时,每个 @RequestMapping
都通过 HandlerMethodMappingNamingStrategy
分配一个默认名称,其默认实现使用类和方法名称首字母(例如,ThingController
中的 getThing
方法变为“TC#getThing”)。如果有名称冲突,您可以使用 @RequestMapping(name="..")
分配显式名称或实现您自己的 HandlerMethodMappingNamingStrategy
。