1. 概述
在这个教程中,我们将学习如何在Spring的*RestTemplate*中编码URI变量。
我们经常遇到的一个编码问题是在URI变量中包含加号(*)。例如,如果我们的URI变量值为http://localhost:8080/api/v1/plus+sign
,那么加号会被编码为空格,可能导致服务器返回意外的结果。
接下来,我们将探讨几种解决这个问题的方法。
2. 项目设置
我们将创建一个使用RestTemplate调用API的小项目。
2.1. Spring Web依赖
首先,让我们在pom.xml
中添加Spring Web Starter依赖:
<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
对象。
如文中所述的代码示例,您可以在GitHub上找到。