RestTemplate.exchange() 将“%”转码成“%25”
URL query中包含时间戳,格式yyyy-MM-dd'T'HH:mm:ss'Z'
,按接口要求对时间戳编码,结果为yyyy-MM-dd'T'HH%3Amm%3Ass'Z'
,使用RestTemplate.exchange()
时出现问题,RestTemplate会对时间戳二次转码,转码后结果为yyyy-MM-dd'T'HH%253Amm%253Ass'Z'
,注意两次转码的区别:
第一次 | 第二次 |
---|---|
HH%3Amm%3Ass | HH%253Amm%253Ass |
原因就在于exchange()
方法会对query转码,%
会被转为%25
。
跟踪代码:
// org.springframework.web.client.RestTemplate
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
@Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
throws RestClientException {
// ...
return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
// 注意getUriTemplateHandler()
URI expanded = getUriTemplateHandler().expand(url, uriVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
// 最终调用到这里
// org.springframework.web.util.DefaultUriBuilderFactory.DefaultUriBuilder#createUri
private URI createUri(UriComponents uric) {
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
// 这里做的转码操作
uric = uric.encode();
}
return URI.create(uric.toString());
}
可以看到uric.encode()
调用前有个判断,若encodingMode = URI_COMPONENT
则进行转码。
至此,解决思路有了:修改encodingMode != URI_COMPONENT
。查看encodingMode
定义:
// org.springframework.web.util.DefaultUriBuilderFactory
private EncodingMode encodingMode = EncodingMode.TEMPLATE_AND_VALUES;
encodingMode
是DefaultUriBuilderFactory
属性,正好有set方法,而getUriTemplateHandler()
返回的是RestTemplate
的属性:
private UriTemplateHandler uriTemplateHandler;
查看一下DefaultUriBuilderFactory
继承关系:
到这里,解决方法就很明确了:
RestTemplate template = new RestTemplate(factory);
//...
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
template.setUriTemplateHandler(uriBuilderFactory);
总结一下:
RestTemplate.exchange()
使用uriTemplateHandler
处理urluriTemplateHandler
的默认实现类是DefaultUriBuilderFactory
DefaultUriBuilderFactory
的expand()
方法调用其内部类DefaultUriBuilder
的build()
方法DefaultUriBuilder.build()
根据DefaultUriBuilderFactory.encodingMode
决定是否url转码DefaultUriBuilderFactory.encodingMode
默认需要转码- 因此可以在
RestTemplate
初始化时手动设置DefaultUriBuilderFactory.encodingMode
,不让其转码
搞定!问题解决!
其他解决方法:
出现这个问题时,我也尝试过网上寻找解决方法,但并不适用我的这种情况,也做个记录吧。
若使用RestTemplate.getForObject()
出现此问题,可以试试以下方法:
// 直接使用String类型的url会被转码,用URI包装一下就可以了
URI uri = new URI(url);
resp = template.getForObject(uri, String.class);