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 範本的靜態 factory 方法。 |
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 範本的靜態 factory 方法。 |
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
,但它不是靜態 factory 方法,而是一個實際的實例,其中包含組態和偏好設定,如下列範例所示
-
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 解析器 — 此解析器基於 URL 解析演算法,位於 WhatWG URL Living standard 中。它針對各種非預期輸入案例提供寬鬆的處理。瀏覽器實作此功能,以便寬鬆地處理使用者輸入的 URL。如需更多詳細資訊,請參閱 URL Living Standard 和 URL 解析 測試案例。
依預設,RestClient
、WebClient
和 RestTemplate
使用 RFC 解析器類型,並預期應用程式提供符合 RFC 語法的 URL 範本。若要變更此設定,您可以自訂任何用戶端上的 UriBuilderFactory
。
應用程式和框架可能會進一步依賴 UriComponentsBuilder
來滿足自身需求,以解析使用者提供的 URL,以便檢查並可能驗證 URI 組件,例如 scheme、host、port、path 和 query。此類組件可以決定使用 WhatWG 解析器類型,以便更寬鬆地處理 URL,並與瀏覽器解析 URI 的方式保持一致,以便在重新導向至輸入 URL 或將其包含在對瀏覽器的回應中時使用。
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 範本。作為 factory,它提供單一位置來組態編碼方法,基於以下編碼模式之一
-
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")
您可以建立相對於 Context 路徑的 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(例如 /main/*
)的 URI,如下列範例所示
-
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()
在上述範例中,我們提供實際的方法引數值(在此案例中為 long 值:21
),以用作路徑變數並插入 URL 中。此外,我們提供值 42
來填寫任何剩餘的 URI 變數,例如從類型層級請求映射繼承的 hotel
變數。如果方法有更多引數,我們可以為 URL 不需要的引數提供 null。一般而言,只有 @PathVariable
和 @RequestParam
引數與建構 URL 相關。
還有其他方法可以使用 MvcUriComponentsBuilder
。例如,您可以使用類似於透過 Proxy 模擬測試的技術,以避免依名稱參照控制器方法,如下列範例所示(範例假設靜態匯入 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 一起用於連結建立時,其設計受到限制。除了需要適當的參數簽章外,傳回類型還存在技術限制(也就是說,為連結建置器調用產生執行階段 Proxy),因此傳回類型不得為 final 。特別是,檢視名稱的常見 String 傳回類型在這裡不起作用。您應該改為使用 ModelAndView 甚至純 Object (具有 String 傳回值)。 |
先前的範例使用 MvcUriComponentsBuilder
中的靜態方法。在內部,它們依賴 ServletUriComponentsBuilder
從目前請求的 scheme、host、port、Context 路徑和 Servlet 路徑準備基本 URL。這在大多數情況下都運作良好。但是,有時可能不足。例如,您可能在請求的 Context 之外(例如準備連結的批次處理程序),或者您可能需要插入路徑前置詞(例如從請求路徑中移除並需要重新插入連結的地區設定前置詞)。
對於這種情況,您可以使用接受 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 標籤函式庫(也就是 META-INF/spring.tld)中宣告的 mvcUrl
函數,但是很容易定義您自己的函數或為其他範本技術準備類似的函數。
以下是其運作方式。在啟動時,每個 @RequestMapping
都會透過 HandlerMethodMappingNamingStrategy
指派預設名稱,其預設實作使用類別和方法名稱的大寫字母(例如,ThingController
中的 getThing
方法會變成 "TC#getThing")。如果有名稱衝突,您可以使用 @RequestMapping(name="..")
指派明確名稱或實作您自己的 HandlerMethodMappingNamingStrategy
。