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
是否是TEMPLATE_AND_VALUES
找到原因,解决起来就简单了,不让它进入这个if就行了,查看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);