此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cadn.net.cn

URI 链接

本节描述了 Spring Framework 中可用于 URI 的各种选项。spring-doc.cadn.net.cn

UriComponents

Spring MVC 和 Spring WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder有助于使用变量从 URI 模板构建 URI,如下例所示:spring-doc.cadn.net.cn

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, 如下例所示:spring-doc.cadn.net.cn

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(这意味着编码)来进一步缩短它, 如下例所示:spring-doc.cadn.net.cn

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 模板进一步缩短它,如下例所示:spring-doc.cadn.net.cn

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

Uri生成器

Spring MVC 和 Spring WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder实现UriBuilder.您可以创建一个UriBuilder,反过来,使用UriBuilderFactory.一起UriBuilderFactoryUriBuilder提供一种可插拔的机制来从 URI 模板构建 URI,基于 共享配置,例如基本 URL、编码首选项和其他详细信息。spring-doc.cadn.net.cn

您可以配置RestTemplateWebClient替换为UriBuilderFactory以自定义 URI 的准备工作。DefaultUriBuilderFactory是默认值 实现UriBuilderFactory使用UriComponentsBuilderinternally 和 公开共享配置选项。spring-doc.cadn.net.cn

以下示例显示如何配置RestTemplate:spring-doc.cadn.net.cn

// 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:spring-doc.cadn.net.cn

// 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但是,它不是静态工厂方法,而是一个实际实例 ,其中包含 configuration 和 preferences,如下例所示:spring-doc.cadn.net.cn

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 WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder支持两种 URI 解析器类型:spring-doc.cadn.net.cn

  1. RFC 解析器 — 此解析器类型要求 URI 字符串符合 RFC 3986 语法, 并将偏离语法的行为视为非法。spring-doc.cadn.net.cn

  2. WhatWG 解析器 — 此解析器基于 WhatWG URL Living 标准中的 URL 解析算法。它提供宽松的处理 各种意外输入的情况。浏览器实现此 API 是为了处理 宽容地用户键入的 URL。有关更多详细信息,请参阅 URL Living Standard 和 URL 解析测试用例spring-doc.cadn.net.cn

默认情况下,RestClient,WebClientRestTemplate使用 RFC 解析器类型,并且 期望应用程序提供符合 RFC 语法的 URL 模板。更改 ,您可以自定义UriBuilderFactory在任何客户端上。spring-doc.cadn.net.cn

应用程序和框架可能进一步依赖于UriComponentsBuilder满足自身需求 解析用户提供的 URL,以便检查和可能验证的 URI 组件 例如 scheme、host、port、path 和 query。此类组件可以决定使用 WhatWG 解析器类型,以便更宽松地处理 URL,并与 浏览器解析 URI,以防重定向到输入 URL 或它包含在 响应浏览器。spring-doc.cadn.net.cn

URI 编码

Spring MVC 和 Spring WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder在两个级别公开编码选项:spring-doc.cadn.net.cn

这两个选项都用转义的八位字节替换非 ASCII 字符和非法字符。但是,第一个选项 还会替换 URI 变量中出现的具有保留含义的字符。spring-doc.cadn.net.cn

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

在大多数情况下,第一个选项可能会给出预期的结果,因为它将 URI 变量作为不透明数据进行完全编码,而第二个选项在 URI 变量确实有意包含保留字符。第二个选项也很有用 当根本不扩展 URI 变量时,因为这也将编码任何 顺便说一句,看起来像一个 URI 变量。spring-doc.cadn.net.cn

以下示例使用第一个选项:spring-doc.cadn.net.cn

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(这意味着编码)来缩短前面的示例, 如下例所示:spring-doc.cadn.net.cn

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 模板进一步缩短它,如下例所示:spring-doc.cadn.net.cn

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策略。两者都可以使用自定义策略 如下例所示:spring-doc.cadn.net.cn

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

DefaultUriBuilderFactoryimplementation usesUriComponentsBuilderinternally 到 展开并编码 URI 模板。作为工厂,它提供了一个配置位置 编码方法,基于以下编码模式之一:spring-doc.cadn.net.cn

  • TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),对应于 前面列表中的第一个选项,用于预编码 URI 模板并在 扩大。spring-doc.cadn.net.cn

  • VALUES_ONLY:不对 URI 模板进行编码,而是应用严格编码 到 URI 变量UriUtils#encodeUriVariables在将它们扩展到 模板。spring-doc.cadn.net.cn

  • URI_COMPONENT:使用UriComponents#encode(),对应于前面列表中的第二个选项,更改为 在 URI 变量展开对 URI 组件值进行编码。spring-doc.cadn.net.cn

  • NONE:不应用编码。spring-doc.cadn.net.cn

RestTemplate设置为EncodingMode.URI_COMPONENT对于历史 原因和向后兼容性。这WebClient依赖于默认值 在DefaultUriBuilderFactory,它已从EncodingMode.URI_COMPONENT在 5.0.x 更改为EncodingMode.TEMPLATE_AND_VALUES在 5.1 中。spring-doc.cadn.net.cn

相对 Servlet 请求

您可以使用ServletUriComponentsBuilder创建相对于当前请求的 URI, 如下例所示:spring-doc.cadn.net.cn

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,如下例所示:spring-doc.cadn.net.cn

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 的 URI(例如/main/*), 如下例所示:spring-doc.cadn.net.cn

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-*headers,用于指定客户端发起的地址。考虑使用ForwardedHeaderFilter提取并使用或丢弃 这样的标头。

Spring MVC 提供了一种机制来准备指向控制器方法的链接。例如 以下 MVC 控制器允许创建链接:spring-doc.cadn.net.cn

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

您可以通过按名称引用方法来准备链接,如下例所示:spring-doc.cadn.net.cn

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

在前面的示例中,我们提供了实际的方法参数值(在本例中为 long 值:21) 用作路径变量并插入到 URL 中。此外,我们还提供 价值42来填写任何剩余的 URI 变量,例如hotel继承的变量 从类型级请求映射。如果该方法有更多的参数,我们可以为 URL 不需要参数。一般来说,只有@PathVariable@RequestParam参数 与构造 URL 相关。spring-doc.cadn.net.cn

还有其他使用MvcUriComponentsBuilder.例如,您可以使用一种技术 类似于通过代理进行模拟测试,以避免通过名称引用控制器方法,如下例所示 (该示例假定 static importMvcUriComponentsBuilder.on):spring-doc.cadn.net.cn

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(使用Stringreturn value)来代替。

前面的示例在MvcUriComponentsBuilder.在内部,他们依赖于 上ServletUriComponentsBuilder要从 scheme、host、port 准备基本 URL, 当前请求的 context path 和 servlet path 的 servlet 路径。这在大多数情况下效果很好。 但是,有时,它可能不够。例如,您可能位于 请求(例如准备链接的批处理),或者可能需要插入路径 前缀(例如已从请求路径中删除的区域设置前缀,需要 重新插入到链接中)。spring-doc.cadn.net.cn

对于这种情况,您可以使用 staticfromXxx接受UriComponentsBuilder以使用基 URL。或者,您可以创建一个MvcUriComponentsBuilder替换为基本 URL,然后使用基于实例的withXxx方法。例如, 以下列出用途withMethodCall:spring-doc.cadn.net.cn

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-*headers,用于指定客户端发起的地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃 这样的标头。

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

请考虑以下示例:spring-doc.cadn.net.cn

@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 准备一个链接,如下所示:spring-doc.cadn.net.cn

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

前面的示例依赖于mvcUrl在 Spring 标记库中声明的函数 (即 META-INF/spring.tld),但定义自己的函数或准备 其他模板技术类似。spring-doc.cadn.net.cn

这是它的工作原理。启动时,每个@RequestMapping被分配了默认名称 通过HandlerMethodMappingNamingStrategy,其默认实现使用 类的大写字母和方法名称(例如,getThingmethod 中ThingController变为 “TC#getThing”)。如果存在名称冲突,您可以使用@RequestMapping(name="..")分配显式名称或实现自己的名称HandlerMethodMappingNamingStrategy.spring-doc.cadn.net.cn