1. 概述
本教程將介紹如何使用 Spring 的 RestTemplate 對 URI 變量進行編碼。
一個常見的編碼問題是 當 URI 變量包含加號(+)時。例如,如果 URI 變量的值為 http://localhost:8080/api/v1/plus+sign,加號將被編碼為空格,這可能導致意外的服務器響應。
讓我們看看幾種解決此問題的方案。
2. 項目設置
我們將創建一個小項目,使用 RestTemplate 調用 API。
2.1. Spring Web 依賴
讓我們首先將 Spring Web Starter 依賴添加到我們的 pom.xml 中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
當然,以下是翻譯後的內容:
或者,我們還可以使用 Spring Initializr 來生成項目並添加依賴。
2.2. RestTemplate Bean
接下來,我們將創建一個 RestTemplate Bean:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3. API 調用
讓我們創建一個服務類,該類調用公共 API http://httpbin.org/get。
該 API 會返回一個包含請求參數的 JSON 響應。例如,如果在瀏覽器中調用 URL https://httpbin.org/get?parameter=springboot,我們將獲得以下響應:
{
"args": {
"parameter": "springboot"
},
"headers": {
},
"origin": "",
"url": ""
}
這裏,args對象包含請求參數。為了簡潔,其他值已省略。
3.1. 服務類
讓我們創建一個服務類,該類調用 API 並返回參數鍵 parameter 的值:
@Service
public class HttpBinService {
private final RestTemplate restTemplate;
public HttpBinService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String get(String parameter) {
String url = "http://httpbin.org/get?parameter={parameter}";
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class, parameter);
Map<String, String> args = (Map<>) response.getBody().get("args");
return args.get("parameter");
}
}get() 方法調用指定的 URL,將響應解析為Map,並從args對象中的parameter字段中檢索值。
3.2. 測試
讓我們測試我們的服務類,使用兩個參數——springboot 和 spring+boot,並檢查響應是否符合預期:
@SpringBootTest
class HttpBinServiceTest {
@Autowired
private HttpBinService httpBinService;
@Test
void givenWithoutPlusSign_whenGet_thenSameValueReturned() throws JsonProcessingException {
String parameterWithoutPlusSign = "springboot";
String responseWithoutPlusSign = httpBinService.get(parameterWithoutPlusSign);
assertEquals(parameterWithoutPlusSign, responseWithoutPlusSign);
}
@Test
void givenWithPlusSign_whenGet_thenSameValueReturned() throws JsonProcessingException {
String parameterWithPlusSign = "spring+boot";
String responseWithPlusSign = httpBinService.get(parameterWithPlusSign);
assertEquals(parameterWithPlusSign, responseWithPlusSign);
}
}
如果運行測試,我們會發現第二個測試失敗。 響應是Spring Boot,而不是Spring+Boot。
4. 使用攔截器與 RestTemplate
我們可以使用攔截器來編碼 URI 變量。
讓我們創建一個實現 ClientHttpRequestInterceptor 接口的類:
public class UriEncodingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequest encodedRequest = new HttpRequestWrapper(request) {
@Override
public URI getURI() {
URI uri = super.getURI();
String escapedQuery = uri.getRawQuery().replace("+", "%2B");
return UriComponentsBuilder.fromUri(uri)
.replaceQuery(escapedQuery)
.build(true).toUri();
}
};
return execution.execute(encodedRequest, body);
}
}我們已實現 intercept() 方法。該方法將在RestTemplate每次請求之前執行。
讓我們分解一下代碼:
- 我們創建了一個新的 HttpRequest 對象,該對象包裝了原始請求。
- 對於這個包裝器,我們覆蓋了 getURI() 方法,以編碼 URI 變量。在本例中,我們用 %2B 替換查詢字符串中的加號。
- 使用 UriComponentsBuilder,我們創建了一個新的 URI 對象,並將查詢字符串替換為編碼後的查詢字符串。
- 我們返回了 intercept() 方法中的編碼請求,該請求將替換原始請求。
4.1. 添加攔截器
接下來,我們需要將攔截器添加到 RestTemplate Bean 中:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new UriEncodingInterceptor()));
return restTemplate;
}
}
如果再次運行測試,我們會發現它通過了。
攔截器提供了修改我們想要修改的任何請求部分的靈活性。 它們對諸如添加額外的標頭或修改請求中的字段等複雜場景尤其有益。
對於更簡單的任務,如我們的示例,我們還可以使用 DefaultUriBuilderFactory 來修改編碼。 讓我們看看如何操作。
5. 使用 DefaultUriBuilderFactory
另一種編碼 URI 變量的方法是通過修改 RestTemplate 內部使用的 DefaultUriBuilderFactory 對象。
默認情況下,URI 構建器首先編碼整個 URL,然後再分別編碼值。我們將創建一個新的 DefaultUriBuilderFactory 對象並將編碼模式設置為 VALUES_ONLY。 這樣就限制了編碼僅限於值。
然後,我們可以使用 setUriTemplateHandler() 方法將新的 DefaultUriBuilderFactory 對象設置為我們的 RestTemplate bean。
讓我們使用它來創建一個新的 RestTemplate bean:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory();
defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
restTemplate.setUriTemplateHandler(defaultUriBuilderFactory);
return restTemplate;
}
}
這是一個URI變量編碼的另一種替代方案。再次運行測試,我們會看到它通過。
6. 結論
在本文中,我們學習瞭如何在 RestTemplate 請求中編碼 URI 變量。我們看到了兩種實現方法:使用攔截器和修改 DefaultUriBuilderFactory 對象。