接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122148706?spm=1001.2014.3001.5502
5、URI 链接
本节介绍 Spring Framework 中可用于处理 URI 的各种选项。
5.1 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");
5.2 UriBuilder
UriComponentsBuilder 实现了 UriBuilder 。 反过来,您可以使用 UriBuilderFactory 创建 UriBuilder 。 UriBuilderFactory 和 UriBuilder 共同提供了一种可插拔机制,可根据共享配置(例如基本 URL、编码首选项和其他详细信息)从 URI 模板构建 URI。
您可以使用 UriBuilderFactory 配置 RestTemplate 和 WebClient 来自定义 URI 的准备。 DefaultUriBuilderFactory 是 UriBuilderFactory 的默认实现,它在内部使用 UriComponentsBuilder 并公开共享配置选项。
以下示例显示了如何配置 RestTemplate :
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
以下示例配置了一个 WebClient :
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");
5.3 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();
您可以通过直接转到 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);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
DefaultUriBuilderFactory 实现在内部使用 UriComponentsBuilder 来扩展和编码 URI 模板。 作为一个工厂,它提供了一个单一的地方来配置编码方法,基于以下编码模式之一:
TEMPLATE_AND_VALUES :使用 UriComponentsBuilder#encode() ,对应于前面列表中的第一个选项,对 URI 模板进行预编码,并在扩展时对 URI 变量进行严格编码。VALUES_ONLY :不对 URI 模板进行编码,而是在将 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 。
5.4 响应 Servlet 请求
您可以使用 ServletUriComponentsBuilder 创建相对于当前请求的 URI,如以下示例所示:
HttpServletRequest request = ...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
您可以创建相对于上下文路径的 URI,如以下示例所示:
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build()
您可以创建相对于 Servlet(例如,/main/* )的 URI,如以下示例所示:
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
从 5.1 开始,ServletUriComponentsBuilder 会忽略来自 Forwarded 和 X-Forwarded-* 标头的信息,这些标头指定了源自客户端的地址。 考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃此类标头。
5.5 链接到控制器
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 来提取和使用或丢弃此类标头。
5.6 视图中的链接
在 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 标记库(即 META-INF/spring.tld )中声明的 mvcUrl 函数,但是很容易定义自己的函数或为其他模板技术准备一个类似的函数。
这是它的工作原理。 启动时,每个 @RequestMapping 通过 HandlerMethodMappingNamingStrategy 分配一个默认名称,其默认实现使用类的大写字母和方法名称(例如 ThingController 中的 getThing 方法变为“TC#getThing ”)。 如果存在名称冲突,您可以使用 @RequestMapping(name="..") 来分配显式名称或实现您自己的 HandlerMethodMappingNamingStrategy 。
文章参考:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-uri-building
|