URI 链接

本节介绍 Spring Framework 中可用于处理 URI 的各种选项。 Unresolved directive in mvc-uri-building.adoc - include::partial$web/web-uris.adoc[leveloffset=+1]

相对 Servlet 请求

您可以使用 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 的 URI(例如,/main/*), 如以下示例所示:

  • 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 来提取、使用或丢弃 此类头。

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

在前面的示例中,我们提供了实际的方法参数值(在本例中为长整型值:21) 用作路径变量并插入到 URL 中。此外,我们提供了值 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()

当控制器方法签名需要用于 fromMethodCall 创建链接时,其设计会受到限制。除了需要正确的参数签名之外, 返回类型也存在技术限制(即,为链接构建器调用生成运行时代理), 因此返回类型不能是 final。特别是, 用于视图名称的常见 String 返回类型在此处不起作用。您应该使用 ModelAndView 甚至普通的 Object(带有 String 返回值)代替。

前面的示例使用 MvcUriComponentsBuilder 中的静态方法。在内部,它们依赖于 ServletUriComponentsBuilder,从当前请求的 scheme、host、port、 context path 和 servlet path 准备基本 URL。这在大多数情况下都有效。 但是,有时可能不够。例如,您可能在请求上下文之外 (例如,准备链接的批处理过程),或者您可能需要插入路径 前缀(例如,已从请求路径中删除并需要 重新插入到链接中的语言环境前缀)。

对于这种情况,您可以使用接受 UriComponentsBuilder 的静态 fromXxx 重载方法来使用基本 URL。 或者,您可以创建一个 MvcUriComponentsBuilder 实例,其中包含基本 URL,然后使用基于实例的 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 版本开始,MvcUriComponentsBuilder 会忽略来自 ForwardedX-Forwarded-* 头的客户端原始地址信息。考虑使用 ForwardedHeaderFilter 来提取、使用或丢弃 此类头。

在 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 标签库中声明的 mvcUrl 函数 (即 META-INF/spring.tld),但很容易定义您自己的函数或为其他模板技术准备一个类似的函数。

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