URI 連結
本節描述了 Spring Framework 中用於處理 URI 的各種可用選項。
UriComponents
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 有助於從帶變數的 URI 模板構建 URI,如下例所示
-
Java
-
Kotlin
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。 |
val uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") (1)
.queryParam("q", "{q}") (2)
.encode() (3)
.build() (4)
val uri = uriComponents.expand("Westin", "123").toUri() (5)
| 1 | 帶 URI 模板的靜態工廠方法。 |
| 2 | 新增或替換 URI 元件。 |
| 3 | 請求對 URI 模板和 URI 變數進行編碼。 |
| 4 | 構建 UriComponents。 |
| 5 | 擴充套件變數並獲取 URI。 |
如以下示例所示,前述示例可以合併成一個鏈,並透過 buildAndExpand 縮短
-
Java
-
Kotlin
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri()
如以下示例所示,您可以透過直接跳轉到 URI(這意味著編碼)來進一步縮短它
-
Java
-
Kotlin
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123")
如以下示例所示,您可以使用完整的 URI 模板進一步縮短它
-
Java
-
Kotlin
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123")
UriBuilder
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 實現了 UriBuilder。您可以反過來使用 UriBuilderFactory 建立 UriBuilder。UriBuilderFactory 和 UriBuilder 共同提供了一種可插拔的機制,用於基於共享配置(例如基本 URL、編碼首選項和其他詳細資訊)從 URI 模板構建 URI。
您可以使用 UriBuilderFactory 配置 RestTemplate 和 WebClient,以自定義 URI 的準備。DefaultUriBuilderFactory 是 UriBuilderFactory 的預設實現,它在內部使用 UriComponentsBuilder 並公開共享配置選項。
以下示例展示瞭如何配置 RestTemplate
-
Java
-
Kotlin
// 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);
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory
以下示例配置了 WebClient
-
Java
-
Kotlin
// 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();
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
val client = WebClient.builder().uriBuilderFactory(factory).build()
此外,您還可以直接使用 DefaultUriBuilderFactory。它類似於使用 UriComponentsBuilder,但它不是靜態工廠方法,而是實際的例項,它包含配置和首選項,如下例所示
-
Java
-
Kotlin
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
val baseUrl = "https://example.com"
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)
val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123")
URI 解析
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 支援兩種 URI 解析器型別
-
RFC 解析器——此解析器型別要求 URI 字串符合 RFC 3986 語法,並將與該語法的偏差視為非法。
-
WhatWG 解析器——此解析器基於 WhatWG URL Living 標準中的 URL 解析演算法。它對各種意外輸入情況提供寬鬆處理。瀏覽器實現此功能是為了寬鬆處理使用者輸入的 URL。有關更多詳細資訊,請參閱 URL Living 標準和 URL 解析測試用例。
預設情況下,RestClient、WebClient 和 RestTemplate 使用 RFC 解析器型別,並期望應用程式提供符合 RFC 語法的 URL 模板。要更改此設定,您可以自定義任何客戶端上的 UriBuilderFactory。
應用程式和框架可以進一步依賴 UriComponentsBuilder 來解析使用者提供的 URL,以檢查並可能驗證 URI 元件,例如方案、主機、埠、路徑和查詢。此類元件可以決定使用 WhatWG 解析器型別,以便更寬鬆地處理 URL,並在重定向到輸入 URL 或將其包含在對瀏覽器的響應中時與瀏覽器解析 URI 的方式保持一致。
URI 編碼
Spring MVC 和 Spring WebFlux
UriComponentsBuilder 在兩個級別上公開編碼選項
-
UriComponentsBuilder#encode():首先預編碼 URI 模板,然後在擴充套件時嚴格編碼 URI 變數。
-
UriComponents#encode():在 URI 變數擴充套件後編碼 URI 元件。
這兩種選項都將非 ASCII 和非法字元替換為轉義八位位元組。但是,第一個選項還會替換 URI 變數中具有保留含義的字元。
| 考慮“;”,它在路徑中是合法的,但具有保留含義。第一個選項在 URI 變數中將“;”替換為“%3B”,但在 URI 模板中不替換。相比之下,第二個選項從不替換“;”,因為它在路徑中是合法字元。 |
對於大多數情況,第一個選項可能會給出預期的結果,因為它將 URI 變數視為不透明資料以完全編碼,而如果 URI 變數確實有意包含保留字元,則第二個選項很有用。當根本不擴充套件 URI 變數時,第二個選項也很有用,因為它也會編碼任何偶然看起來像 URI 變數的內容。
以下示例使用第一個選項
-
Java
-
Kotlin
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"
val 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(這意味著編碼)來縮短前面的示例
-
Java
-
Kotlin
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar");
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar")
如以下示例所示,您可以使用完整的 URI 模板進一步縮短它
-
Java
-
Kotlin
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");
val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar")
WebClient 和 RestTemplate 透過 UriBuilderFactory 策略在內部擴充套件和編碼 URI 模板。兩者都可以配置自定義策略,如下例所示
-
Java
-
Kotlin
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();
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}
// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
uriTemplateHandler = factory
}
// Customize the WebClient..
val 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,如下例所示
-
Java
-
Kotlin
HttpServletRequest request = ...
// Re-uses scheme, host, port, path, and query string...
URI uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build("123");
val request: HttpServletRequest = ...
// Re-uses scheme, host, port, path, and query string...
val uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build("123")
您可以建立相對於上下文路徑的 URI,如下例所示
-
Java
-
Kotlin
HttpServletRequest request = ...
// Re-uses scheme, host, port, and context path...
URI uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri();
val request: HttpServletRequest = ...
// Re-uses scheme, host, port, and context path...
val uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri()
您可以建立相對於 Servlet 的 URI(例如,/main/*),如下例所示
-
Java
-
Kotlin
HttpServletRequest request = ...
// Re-uses scheme, host, port, context path, and Servlet mapping prefix...
URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri();
val request: HttpServletRequest = ...
// Re-uses scheme, host, port, context path, and Servlet mapping prefix...
val uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri()
從 5.1 開始,ServletUriComponentsBuilder 忽略來自 Forwarded 和 X-Forwarded-* 標頭的資訊,這些標頭指定了客戶端發起的地址。考慮使用ForwardedHeaderFilter 來提取並使用或丟棄此類標頭。 |
指向控制器的連結
Spring MVC 提供了一種準備指向控制器方法的連結的機制。例如,以下 MVC 控制器允許建立連結
-
Java
-
Kotlin
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
@Controller
@RequestMapping("/hotels/{hotel}")
class BookingController {
@GetMapping("/bookings/{booking}")
fun getBooking(@PathVariable booking: Long): ModelAndView {
// ...
}
}
您可以透過按名稱引用方法來準備連結,如下例所示
-
Java
-
Kotlin
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
val uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42)
val uri = uriComponents.encode().toUri()
在前面的示例中,我們提供了實際的方法引數值(在本例中為長值:21),用作路徑變數並插入到 URL 中。此外,我們提供了值 42,以填充任何剩餘的 URI 變數,例如從型別級別請求對映繼承的 hotel 變數。如果方法有更多引數,我們可以為 URL 不需要的引數提供 null。通常,只有 @PathVariable 和 @RequestParam 引數與構造 URL 相關。
還有其他使用 MvcUriComponentsBuilder 的方法。例如,您可以使用類似於透過代理進行模擬測試的技術,以避免按名稱引用控制器方法,如下例所示(示例假定靜態匯入 MvcUriComponentsBuilder.on)
-
Java
-
Kotlin
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
val uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)
val uri = uriComponents.encode().toUri()
控制器方法簽名在設計時如果需要用於使用 fromMethodCall 建立連結,則會受到限制。除了需要適當的引數簽名外,對返回型別還有技術限制(即為連結構建器呼叫生成執行時代理),因此返回型別不能是 final。特別是,用於檢視名稱的常見 String 返回型別在這裡不起作用。您應該改用 ModelAndView 甚至普通的 Object(帶有 String 返回值)。 |
前面的示例使用 MvcUriComponentsBuilder 中的靜態方法。在內部,它們依賴 ServletUriComponentsBuilder 根據當前請求的方案、主機、埠、上下文路徑和 Servlet 路徑準備基本 URL。這在大多數情況下都有效。然而,有時這可能不夠。例如,您可能在請求上下文之外(例如準備連結的批處理過程),或者您可能需要插入路徑字首(例如已從請求路徑中刪除並需要重新插入到連結中的區域設定字首)。
對於此類情況,您可以使用接受 UriComponentsBuilder 以使用基本 URL 的靜態 fromXxx 過載方法。或者,您可以使用基本 URL 建立 MvcUriComponentsBuilder 的例項,然後使用基於例項的 withXxx 方法。例如,以下列表使用 withMethodCall
-
Java
-
Kotlin
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();
val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en")
val builder = MvcUriComponentsBuilder.relativeTo(base)
builder.withMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)
val uri = uriComponents.encode().toUri()
從 5.1 開始,MvcUriComponentsBuilder 忽略來自 Forwarded 和 X-Forwarded-* 標頭的資訊,這些標頭指定了客戶端發起的地址。考慮使用ForwardedHeaderFilter 來提取並使用或丟棄此類標頭。 |
檢視中的連結
在 Thymeleaf、FreeMarker 或 JSP 等檢視中,您可以透過引用每個請求對映的隱式或顯式分配的名稱來構建指向帶註解控制器的連結。
考慮以下示例
-
Java
-
Kotlin
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
@RequestMapping("/people/{id}/addresses")
class PersonAddressController {
@RequestMapping("/{country}")
fun getAddress(@PathVariable country: String): HttpEntity<PersonAddress> { ... }
}
給定前面的控制器,您可以從 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。