21.7 URI的组建
Spring MVC提供了一种机制,可以使用UriComponentsBuilder和UriComponents来组建并编码URI。
如下例所示,你能扩展并编码一个URI模板的字符串:
UriComponents uriComponents =UriComponentsBuilder.fromUriString(
"http://example.com/hotels/{hotel}/bookings/{booking}").build();
URI uri = uriComponents.expand("42", "21").encode().toUri();
注意,UriComponents是不可变的,而expand()和encode()操作,在必要的时候,会返回新的实例。
你也可以使用独立的URI组件来扩展并编码URI:
UriComponents uriComponents =UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
在Servlet环境下,ServletUriComponentBuilder的子类提供了一个静态工厂方法,这个方法可以从Servlet请求中,复制有效的URL信息。如下:
HttpServletRequest request = ...
// 重用原request中的host,scheme格式,端口,路径和查询参数的字符串
// 替换掉"accountId" 的查询参数值
ServletUriComponentsBuilder ucb =ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
此外,你也可以选择拷贝可用信息中的一部分,并追加到context路径后面:
// 重用host,port(端口)和context路径
// 把"/accounts"追加到路径后面
ServletUriComponentsBuilder ucb =ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build()
或者,在DispatcherServlet已基于名字进行了映射时(比如/main/*这种),你也可以获得servlet映射所包含的部分值,如下:
// 重用 host, port,context path
// 追加servlet映射到路径下的部分值
// 追加"/accounts" 到路径上
ServletUriComponentsBuilder ucb =ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
21.7.1 组建连接到controller和方法的URI
Spring MVC提供了一种机制,能组建连接到controller的方法中的链接,如下例所示:
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。例如:你可以使用某种类似mock测试的技术,这种技术通过代理来避免基于名称来参考匹配控制器的方法。(下面的例子假设静态导入了MvcUriComponentsBuilder.on):
UriComponents uriComponents =MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
上面的例子使用MvcUriComponentsBuilder中的静态方法。在这个方法内部,它们依赖于ServletUriComponentsBuilder来从现存的请求中,根据它的scheme(结构), host, port(端口),context路径和servlet路径,准备好URL。再多数情况下,这样效果很好,但有些情况下,可能会效率低下。例如:你可能会在请求的context的外面(比如:链接准备时的批量处理过程)或者你需要插入一个路径的前缀(例如: 本地前缀从请求路径中被移除了,并且需要重新添加到链接中)。
在这种情况下,你可以使用静态的”fromXxx”重载的方法来允许一个UriComponentsBuilder来使用URL。或者,你可以创建一个带有URL的MvcUriComponentsBuilder的实例,然后使用这个实例的”withXxx”的方法,例如:
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();
21.7.2 在视图中组建连接到controller和方法中的URI
你同样可以从类似JSP,Thymeleaf, FreeMarker等视图中,组建链接到注解式controller中。可以通过使用MvcUriComponentsBuilder中的fromMappingName方法来实现,这个方法是基于名字来进行映射的。
每一个@RequestMapping都被赋予了一个默认名,这个名字默认是类名中的大写字母加上方法名的全称。例如,有个方法叫”getFoo”,这个方法在FooController中,那么这个默认名就是”FC#getFoo”。这种命名的策略还可以通过创建HandlerMethodMappingNamingStrategy的实例,并把这个实例加入进你的RequestMappingHandlerMapping中来进行自定义。这种默认策略的实现,也会在@RequestMapping中的name属性存在时进行使用。这表示,默认指定的映射名与另一个名字冲突的时候(比如:重载的方法),你可以在@RequestMapping上显式地为其指定一个名字。
请注意:
这些被指定了的请求映射名会在启动时的TRACE级别记录日志。
Spring的JSP标签库提供了一个叫做mvcUrl的功能,这个功能能基于它的的机制,来把需要连接到controller方法的链接准备到位。如下例:
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity getAddress(@PathVariable String country) { ... }
}
你能够为其准备好一个链接,如下:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
Get Address
上面的例子中,依赖了在JSP中声明了Spring标签库(比如META-INF/spring.tld)的 mvcUrl这个功能。若需处理更多进阶的情况(比如在上一节讲到过的自定义URL),为了使用拥有一个自定基础URL的特定的MvcUriComponentsBuilder实例,来自定义一个功能,或者是使用自定义的标签文件,都是很方便的。