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%3AssHH%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;

encodingModeDefaultUriBuilderFactory属性,正好有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处理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);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值