Spring MVC 提供了一种机制来准备控制器方法的链接。例如,下面的 MVC 控制器允许创建链接:
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
你可以通过引用方法的名称来准备一个链接,如下例所示:
UriComponents uriComponents = MvcUriComponentsBuilder
// 需要注意的是:由于底层调用了 RequestContextHolder.getRequestAttributes()
// 所以感觉这个功能只能在 请求中使用
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
在前面的例子中,我们提供了实际的方法参数值(在这种情况下,长值:21),以作为路径变量并插入到 URL 中。此外,我们还提供了一个值,42,以填补任何剩余的 URI 变量,例如从类型级请求映射中继承的 hotel 变量。如果该方法有更多的参数,我们可以为 URL 中不需要的参数提供null。一般来说,只有 @PathVariable
和 @RequestParam
参数与构建URL有关。
还有其他方法可以使用 MvcUriComponentsBuilder。例如,你可以使用一种类似于通过代理进行模拟测试的技术来避免引用控制器方法的名称,正如下面的例子所示(该例子假设静态导入 MvcUriComponentsBuilder.on):
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
:::info 当控制器方法签名被认为可用于 fromMethodCall 的链接创建时,其设计是有限的。除了需要一个合适的参数签名外,对返回类型也有技术限制(即为链接创建器的调用生成一个运行时代理),所以 返回类型必须不是最终的(比如返回一个 String 类型的参数就会报错)。特别是,常见的视图名称的 String 返回类型在这里不起作用。你应该使用 ModelAndView,甚至是普通的 Object(有一个字符串返回值)来代替。 :::
先前的例子使用了 MvcUriComponentsBuilder 的静态方法。在内部,它们依靠 ServletUriComponentsBuilder 从当前请求 的 scheme、主机、端口、上下文路径和 Servlet 路径中准备一个 基础 URL。这在大多数情况下效果很好。然而,有时,这可能是不够的。例如,你可能在一个请求的上下文之外(比如一个准备链接的批处理过程),或者也许你需要插入一个路径前缀(比如一个从请求路径中删除的 locale 前缀,需要重新插入到链接中)。
对于这种情况,你可以使用接受 UriComponentsBuilder 的静态 fromXxx 重载方法来使用一个基础 URL。或者,你可以用一个基本的 URL 创建一个MvcUriComponentsBuilder 的实例,然后使用基于实例的 withXxx 方法。例如,下面的列表使用了 withMethodCall:
// 这里同样调用了当前 上下文的功能,需要在一个请求中
// 可以使用其他不需要上下文绑定的方法来实现,比如 ServletUriComponentsBuilder.fromHttpUrl("http://localhost")
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
UriComponents uriComponents = builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
:::info 从 5.1 开始,MvcUriComponentsBuilder 忽略了来自 Forwarded 和 X-Forwarded-* 头的信息,这些头指定了客户端的来源地址。考虑使用ForwardedHeaderFilter 来提取和使用或丢弃这些头信息。 :::