RestTemplate.exchange() 将“%”转码成“%25”

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是否是TEMPLATE_AND_VALUES

找到原因,解决起来就简单了,不让它进入这个if就行了,查看encodingMode定义:

// org.springframework.web.util.DefaultUriBuilderFactory
private EncodingMode encodingMode = EncodingMode.TEMPLATE_AND_VALUES;

encodingModeDefaultUriBuilderFactory属性,正好有set方法,而getUriTemplateHandler()返回的是RestTemplate的属性:

private UriTemplateHandler uriTemplateHandler;

查看一下DefaultUriBuilderFactory继承关系:
RestTemplate.exchange() 将“%”转码成“%25”_第1张图片

到这里,解决方法就很明确了:

RestTemplate template = new RestTemplate(factory);
//...

DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
template.setUriTemplateHandler(uriBuilderFactory);

总结一下:

  • RestTemplate.exchange()使用uriTemplateHandler处理url
  • uriTemplateHandler的默认实现类是DefaultUriBuilderFactory
  • DefaultUriBuilderFactoryexpand()方法调用其内部类DefaultUriBuilderbuild()方法
  • 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);

你可能感兴趣的:(java,spring)