SpringMVC(11) - 构建URI

参考:https://docs.spring.io/spring/docs/4.3.20.RELEASE/spring-framework-reference/htmlsingle/#mvc-uri-building

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组件进行扩展和编码:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

在Servlet环境中,ServletUriComponentsBuilder子类提供静态工厂方法来从Servlet请求中复制可用的URL信息:

HttpServletRequest request = ...

// Re-use host, scheme, port, path and query string
// Replace the "accountId" query param

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

或者,可以选择复制可用信息的子集,包括上下文路径:

// Re-use host, port and context path
// Append "/accounts" to the path

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()

或者在按名称映射DispatcherServlet的情况下(例如/main/*),还可以包含servlet映射的文字部分:

// Re-use host, port, context path
// Append the literal part of the servlet mapping to the path
// Append "/accounts" to the path

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()

1. 构建控制器和方法的URI

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

在上面的例子中,提供了实际的方法参数值,在这种情况下是long值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从当前请求的schema、host、port,context path和servlet path准备基本URL。这在大多数情况下效果很好,但有时可能不够。例如,可能在请求的上下文之外(例如,准备链接的批处理)或者可能需要插入路径前缀(例如,从请求路径中移除区域设置前缀,需要重新插入到链接中)。

对于这种情况,可以使用接受UriComponentsBuilder的静态“fromXxx”重载方法来使用基本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();

2. 使用“Forwarded”和“X-Forwarded-*”头
当请求通过代理(例如负载平衡器)时,host、port和schema可能会发生变化,这对需要创建资源链接的应用程序提出了挑战,因为从客户端角度来看,链接应该反映原始请求的host、port和schema。

RFC 7239为代理定义了“Forwarded”HTTP头,用于提供有关原始请求的信息。还有其他非标准头正在使用中,例如“X-Forwarded-Host”,“X-Forwarded-Port”和“X-Forwarded-Proto”。

ServletUriComponentsBuilder和MvcUriComponentsBuilder都检测、提取和使用来自“Forwarded”头或“X-Forwarded-Host”,“X-Forwarded-Port”和“X-Forwarded-Proto”的信息,这样即使“Forwarded”不存在时,生成的链接也能反映原始请求。

ForwardedHeaderFilter为整个应用程序提供了一次和全局的替代方法。过滤器包装请求以覆盖host、port和schema信息,并“隐藏”任何转发的请求头以供后续处理。

请注意,使用转发头时需要考虑安全性。在应用程序级别,很难确定转发的头是否可信。这就是为什么应该正确配置网络上游以从外部过滤掉不受信任的转发报头的原因。

没有代理但不需要使用转发头的应用程序可以配置ForwardedHeaderFilter以删除和忽略此类头部。

3. 从视图构建控制器和方法的URI
还可以从JSP、Thymeleaf、FreeMarker等视图构建带注解控制器的链接。这可以使用MvcUriComponentsBuilder中的fromMappingName方法来完成,该方法通过名称引用映射。

每个@RequestMapping都会根据类的大写字母和完整的方法名称分配一个默认名称。例如,类FooController中的方法getFoo被赋予名称 “FC#getFoo” 。可以通过创建HandlerMethodMappingNamingStrategy的实例并将其插入RequestMappingHandlerMapping来替换或自定义此策略。默认策略实现还会查看@RequestMapping上的name属性,并使用该属性(如果存在)。这意味着如果指定的默认映射名称与另一个(例如重载方法)冲突,则可以在@RequestMapping上明确指定名称。
注:分配的请求映射名称在启动时TRACE级别日志上记录。

Spring JSP标签库提供了一个名为mvcUrl的函数,可用于根据此机制准备指向控制器方法的链接。

例如给出:

@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

    @RequestMapping("/{country}")
    public HttpEntity 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 JSP函数。 对于更高级的情况(例如,如上一节中所述的自定义基本URL),可以轻松定义自己的函数或使用自定义标签文件,以便将MvcUriComponentsBuilder的特定实例与自定义基本URL一起使用。

猜你喜欢

转载自blog.csdn.net/mytt_10566/article/details/84184698
URI
今日推荐