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一起使用。