原文鏈接:https://www.codemore.top/cates/Backend/post/2018-04-22/spring-mvc-uri-links
這一節主要講的是Spring Framework組成URI的各種選項。
UriComponents
UriComponents
和java.net.URI
類似。但是UriComponents
有一個專用的UriComponentsBuilder
來構建,並且支持模版變量。例如
String uriTemplate = "http://example.com/hotels/{hotel}"; UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate) 1 .queryParam("q", "{q}") 2 .build(); 3 URI uri = uriComponents.expand("Westin", "123").encode().toUri(); 4
解釋:
- 包含URI模版的靜態工廠方法
- 添加或者替代URI組件
- 構建
UriComponents
- 擴展URI變量,編碼獲取URI變量
上述步驟合起來可以如下表示:
String uriTemplate = "http://example.com/hotels/{hotel}"; URI uri = UriComponentsBuilder.fromUriString(uriTemplate) .queryParam("q", "{q}") .buildAndExpand("Westin", "123") .encode() .toUri();
UriBuilder
UriComponentsBuilder
是UriBuilder
的子類。UriBuilderFactory
和UriBuilder
合在一起提供了通過URI模版獲取URI
的一套可插拔的機制,同樣也提供了一種共享公共屬性,基礎URI,編碼策略等的方式。 RestTemplate
和WebClient
都可以通過UriBuilderFactory
配置。默認實現依賴於內部的UriComponentsBuilder
,同時提供選項配置基礎URI,編碼策略等。 RestTemplate
方式:
String baseUrl = "http://example.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory);
WebClient
方式:
String baseUrl = "http://exaplme.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); // Configure the UriBuilderFactory.. WebClient client = WebClient.builder().uriBuilderFactory(factory).build(); // Or use shortcut on builder.. WebClient client = WebClient.builder().baseUrl(baseUrl).build(); // Or use create shortcut... WebClient client = WebClient.create(baseUrl);
同樣像直接使用UriComponentsBuilder
一樣,也可以直接使用DefaultUriBuilderFactory
。唯一的區別是DefaultUriBuilderFactory
是無狀態的,並且可以重用。例如:
String baseUrl = "http://example.com"; DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl); URI uri = uriBuilderFactory.uriString("/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123"); // encoding strategy applied..
URI 編碼
在UriComponets
中編碼URI的步驟如下:
- 擴展URI變量
- 每個URI組件(path,query等)都單獨編碼
編碼規則如下:對所有非法字符應用百分號編碼方式,包括非ASCII的字符和在RFC 3986中定義的非法字符。 上述編碼規則並沒有對有保留意義的字符編碼,只是對URI組件中的非法字符盡心編碼,如果想要對其編碼,可以修改編碼策略使其全部編碼。 當使用 DefaultUriBuilderFactory
(在WebClient
,RestTemplate
中使用或者直接使用)時可以如下選擇編碼方式:
String baseUrl = "http://example.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl) factory.setEncodingMode(EncodingMode.VALUES_ONLY);
Servlet 請求
可以使用ServletUriComponentsBuilder
來創建相對於當前請求的URI,例如:
HttpServletRequest request = ...
// Re-uses host, scheme, port, path and query string... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}").build() .expand("123") .encode();
創建相對於當前context的URI
// Re-uses host, port and context path... ServletUriComponentsBuilder ucb = ServletUriCompontentsBuilder.fromContextPath(request) .path("/accounts").build();
創建相對於Servlet(例如:/main/*)的URI
// Re-uses host, port, context path, and Servlet prefix... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts").build()
controller的鏈接
Spring MVC使用MvcUriComponentsBuilder
為controller創建鏈接,例如由如下一個controller:
@Controller @RequestMapping("/hotels/{hotel}") public class BookingController { @GetMapping("/bookings/{booking}") public String getBooking(@PathVariable Long booking) { // ... } }
可以根據名字來生成一個鏈接:
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
在上面這個例子中,直接提供了函數的參數值,21被賦值給路徑變量,42賦值給從類的@RequestMapping
繼承過來的查詢參數{hotel}
。 MvcUriComponentsBuilder
的另一個用處是可以mock一個測試,例如:
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
上面的例子大多數情況下運行良好,但是也有不足,例如:可能會在request上下文外創建uri,或者是需要插入一個路徑前綴(比如測試的時候通過前綴測試,但是正式上線后直接是根(/))。對於這種情況可以使用靜態函數fromXxx
這種進行處理:
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();
視圖中的鏈接
可以通過MvcUriComponentsBuilder
的fromMappingName
在視圖(例如:JSP,Thymleaf,FreeMarker等)中構建指向controller的鏈接。 每個@RequestMapping
的方法都會有一個名字,這個名字基於controller類名的大些字母加上‘#’號再加上方法名構成,例如,FooController
類中的getFoo
方法的名字就是FC#getFoo
。當然這個可以通過修改HandlerMethodMappingNamingStrategy
來定制其名字。默認策略同樣也會去查找@RequestMapping
中設置名字的屬性,如果提供了,則會使用其設置的名字。 Spring JSP的tag庫提供了一個mvcUrl
的方法,這個可以用來構建指向controller的方法。例如有如下controller:
@RequestMapping("/people/{id}/addresses") public class PersonAddressController { @RequestMapping("/{country}") public HttpEntity getAddress(@PathVariable String country) { ... } }
JSP中可以如下來構建指向其URI
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> ... <a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>