URI Links
This section describes various options available in the TODAY Framework to work with URI’s.
UriComponents
UriComponentsBuilder
有助于从带有变量的 URI 模板构建 URI,如下例所示:
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") (1)
.queryParam("q", "{q}") (2)
.encode() (3)
.build(); (4)
URI uri = uriComponents.expand("Westin", "123").toUri(); (5)
1 | 带 URI 模板的静态工厂方法。 |
2 | 添加或替换 URI 组件。 |
3 | 请求对 URI 模板和 URI 变量进行编码。 |
4 | 构建 UriComponents 。 |
5 | 展开变量并获取 URI 。 |
前面的示例可以合并为一个链,并使用 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
。您可以创建一个 UriBuilder
,
进而使用 UriBuilderFactory
。UriBuilderFactory
和 UriBuilder
一起提供了一个可插拔的机制,
用于根据共享配置(如基础 URL、编码偏好和其他详细信息)从 URI 模板构建 URI。
您可以配置 RestTemplate
和 WebClient
使用 UriBuilderFactory
来自定义 URI 的准备。
DefaultUriBuilderFactory
是 UriBuilderFactory
的默认实现,它在内部使用 UriComponentsBuilder
并公开共享配置选项。
以下示例展示了如何配置 RestTemplate
:
// import cn.taketoday.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 cn.taketoday.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 Encoding
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
:不应用编码。
Relative Servlet Requests
You can use ServletUriComponentsBuilder
to create URIs relative to the current request,
as the following example shows:
HttpServletRequest request = ...
// Re-uses scheme, host, port, path, and query string...
URI uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build("123");
You can create URIs relative to the context path, as the following example shows:
HttpServletRequest request = ...
// Re-uses scheme, host, port, and context path...
URI uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri();
You can create URIs relative to a Servlet (for example, /main/*
),
as the following example shows:
-
Java
HttpServletRequest request = ...
// Re-uses scheme, host, port, context path, and Servlet mapping prefix...
URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri();
Links to Controllers
Web MVC provides a mechanism to prepare links to controller methods. For example, the following MVC controller allows for link creation:
-
Java
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
You can prepare a link by referring to the method by name, as the following example shows:
-
Java
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
In the preceding example, we provide actual method argument values (in this case, the long value: 21
)
to be used as a path variable and inserted into the URL. Furthermore, we provide the
value, 42
, to fill in any remaining URI variables, such as the hotel
variable inherited
from the type-level request mapping. If the method had more arguments, we could supply null for
arguments not needed for the URL. In general, only @PathVariable
and @RequestParam
arguments
are relevant for constructing the URL.
There are additional ways to use MvcUriComponentsBuilder
. For example, you can use a technique
akin to mock testing through proxies to avoid referring to the controller method by name, as the following example shows
(the example assumes static import of MvcUriComponentsBuilder.on
):
-
Java
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
Controller method signatures are limited in their design when they are supposed to be usable for
link creation with fromMethodCall . Aside from needing a proper parameter signature,
there is a technical limitation on the return type (namely, generating a runtime proxy
for link builder invocations), so the return type must not be final . In particular,
the common String return type for view names does not work here. You should use ModelAndView
or even plain Object (with a String return value) instead.
|
The earlier examples use static methods in MvcUriComponentsBuilder
. Internally, they rely
on ServletUriComponentsBuilder
to prepare a base URL from the scheme, host, port,
context path, and mockApi path of the current request. This works well in most cases.
However, sometimes, it can be insufficient. For example, you may be outside the context of
a request (such as a batch process that prepares links) or perhaps you need to insert a path
prefix (such as a locale prefix that was removed from the request path and needs to be
re-inserted into links).
For such cases, you can use the static fromXxx
overloaded methods that accept a
UriComponentsBuilder
to use a base URL. Alternatively, you can create an instance of MvcUriComponentsBuilder
with a base URL and then use the instance-based withXxx
methods. For example, the
following listing uses withMethodCall
:
-
Java
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();
Links in Views
In views such as Thymeleaf, FreeMarker, or JSP, you can build links to annotated controllers by referring to the implicitly or explicitly assigned name for each request mapping.
Consider the following example:
-
Java
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
Given the preceding controller, you can prepare a link from a JSP, as follows:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>
The preceding example relies on the mvcUrl
function declared in the Infra tag library
(that is, META-INF/spring.tld), but it is easy to define your own function or prepare a
similar one for other templating technologies.
Here is how this works. On startup, every @RequestMapping
is assigned a default name
through HandlerMethodMappingNamingStrategy
, whose default implementation uses the
capital letters of the class and the method name (for example, the getThing
method in
ThingController
becomes "TC#getThing"). If there is a name clash, you can use
@RequestMapping(name="..")
to assign an explicit name or implement your own
HandlerMethodMappingNamingStrategy
.