Spring Web MVC:URI链接
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-uri-building.html
UriComponents
UriComponentsBuilder有助于从带有变量的URI模板构建URI,如下例所示:
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.build();
URI uri = uriComponents.expand("Westin", "123").toUri();
前面的例子可以合并成一个链条,并通过buildAndExpand缩短,如下例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();
可以通过直接转到URI(这意味着编码)来进一步缩短它,如下例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
可以使用完整的URI模板进一步缩短它,如下例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
UriBuilder
UriComponentsBuilder实现了UriBuilder。也可以使用UriBuilderFactory反过来创建UriBuilder。UriBuilderFactory和UriBuilder一起提供了一个可插拔的机制,用于根据共享配置(如基本URL、编码偏好设置和其他细节)从URI模板构建URI。
可以使用UriBuilderFactory配置RestTemplate和WebClient来自定义URI的准备。DefaultUriBuilderFactory是UriBuilderFactory的默认实现,它在内部使用UriComponentsBuilder并公开共享配置选项。
以下示例展示了如何配置RestTemplate:
// 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);
以下示例配置了一个WebClient:
// 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();
此外,还可以直接使用DefaultUriBuilderFactory。它类似于使用UriComponentsBuilder,但与其静态工厂方法不同,它是一个实际的实例,持有配置和偏好设置,如下例所示:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
URI 编码
UriComponentsBuilder在两个层次上公开了编码选项:
- UriComponentsBuilder#encode():首先对URI模板进行预编码,然后在扩展时严格编码URI变量。
- UriComponents#encode():在扩展URI变量后对URI组件进行编码。
这两个选项都会用转义的八进制字节替换非ASCII和非法字符。然而,第一个选项还会替换在URI变量中出现的具有保留意义的字符。
考虑到分号(;),它在路径中是合法的,但具有保留意义。第一个选项会在URI变量中将分号替换为%3B,但在URI模板中不会这么做。相比之下,第二个选项从不替换分号,因为它在路径中是一个合法字符。
在大多数情况下,第一个选项可能会给出预期的结果,因为它将URI变量视为不透明的数据,需要进行完整编码,而第二个选项在URI变量确实包含保留字符时才有用。当根本不扩展URI变量时,第二个选项也很有用,因为这也会编码任何偶然看起来像URI变量的内容。
以下示例使用第一个选项:
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"
可以通过直接转到URI(这意味着编码)来缩短前面的例子,如下例所示:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar");
还可以使用完整的URI模板进一步缩短它,如下例所示:
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");
WebClient和RestTemplate通过UriBuilderFactory策略在内部扩展和编码URI模板。两者都可以配置自定义策略,如下例所示:
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();
DefaultUriBuilderFactory实现在内部使用UriComponentsBuilder来扩展和编码URI模板。作为一个工厂,它提供了一个单一的地方来配置基于以下编码模式之一的编码方法:
- TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),对应于前面列表中的第一个选项,对URI模板进行预编码,并在扩展时严格编码URI变量。
- VALUES_ONLY:不对URI模板进行编码,而是在将它们扩展为模板之前,通过UriUtils#encodeUriVariables对URI变量应用严格编码。
- URI_COMPONENT:使用UriComponents#encode(),对应于前面列表中的第二个选项,在扩展URI变量后对URI组件值进行编码。
- NONE:不应用编码。
RestTemplate由于历史原因和向后兼容性被设置为EncodingMode.URI_COMPONENT。WebClient依赖于DefaultUriBuilderFactory中的默认值,该值在5.0.x版本中从EncodingMode.URI_COMPONENT更改为5.1版本中的EncodingMode.TEMPLATE_AND_VALUES。
相对Servlet请求
可以使用ServletUriComponentsBuilder来创建相对于当前请求的URI,如下例所示:
HttpServletRequest request = ...
// Re-uses scheme, host, port, path, and query string...
URI uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build("123");
可以创建相对于上下文路径的URI,如下例所示:
HttpServletRequest request = ...
// Re-uses scheme, host, port, and context path...
URI uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri();
可以创建相对于Servlet的URI(例如,/main/*),如下例所示:
HttpServletRequest request = ...
// Re-uses scheme, host, port, context path, and Servlet mapping prefix...
URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri();
从5.1版本开始,ServletUriComponentsBuilder忽略了来自Forwarded和X-Forwarded-*头部的信息,这些信息指定了客户端发起的地址。请考虑使用ForwardedHeaderFilter来提取和使用或丢弃此类头部。
链接到控制器(Links to Controllers)
Spring MVC 提供了一种机制来准备链接到控制器方法。例如,以下 MVC 控制器允许创建链接:
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
可以通过名称引用方法来准备链接,如下例所示:
UriComponents uriComponents = MvcUriComponentsBuilder
.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();
当控制器方法签名需要用于通过 fromMethodCall 创建链接时,它们的设计受到一些限制。除了需要正确的参数签名外,返回类型也存在一个技术限制(即,为链接构建器调用生成运行时代理),因此返回类型不能是 final 的。特别是,常见的用于视图名称的 String 返回类型在这里不适用。你应该使用 ModelAndView 或者甚至是普通的 Object(带有 String 返回值)来代替。
前面的示例使用了 MvcUriComponentsBuilder 中的静态方法。在内部,它们依赖于 ServletUriComponentsBuilder 来从当前请求的协议、主机、端口、上下文路径和 servlet 路径准备基础 URL。这在大多数情况下都能很好地工作。然而,有时这可能不够用。例如,你可能不在请求的上下文中(如批量处理过程中准备链接),或者你可能需要插入一个路径前缀(如从请求路径中移除的区域设置前缀,需要重新插入到链接中)。
对于这样的情况,你可以使用接受 UriComponentsBuilder 的静态 fromXxx 重载方法,以便使用基础 URL。或者,你可以使用带有基础 URL 的 MvcUriComponentsBuilder 实例,然后使用基于实例的 withXxx 方法。例如,以下列表使用 withMethodCall:
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();
自 5.1 版本起,MvcUriComponentsBuilder 忽略了来自 Forwarded 和 X-Forwarded-* 头部的信息,这些头部指定了客户端原始地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃这些头部信息。
视图中的链接(Links in Views)
在 Thymeleaf、FreeMarker 或 JSP 等视图中,可以通过引用为每个请求映射隐式或显式分配的名称,来构建指向带注解的控制器的链接。
请考虑以下示例:
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
根据前面的控制器,你可以在 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。
更多推荐



所有评论(0)