關(guān)于遠(yuǎn)程調(diào)用RestTemplate的使用避坑指南
一、前言介紹
RestTemplate是Spring中用于遠(yuǎn)程接口調(diào)用的工具類,它是Apache的HttpClient的模板封裝,使用起來(lái)非常方便,本文將講述這兩天自己在使用RestTemplate過(guò)程中遇到的問(wèn)題,當(dāng)然這些問(wèn)題也是由于自己對(duì)RestTemplate工具類了解不夠全面不夠透徹造成的,希望自己遇到的這些問(wèn)題能為大家提前避雷或是遇到類似問(wèn)題時(shí)的一個(gè)解決參考。
二、 問(wèn)題記錄
1. 慎!【url參數(shù)中有json字符串】
在使用RestTemplate進(jìn)行遠(yuǎn)程接口調(diào)用時(shí),如果url拼接參數(shù)中json字符串時(shí)一定要小心,使用場(chǎng)景如下:利用restTemplate調(diào)用user的查詢信息接口,url中的一個(gè)參數(shù)user為json字符串格式{\"user\":\"xiaoming,\"age\":"12"}
// JSON參數(shù) Map<String, String> paramMap = new HashMap<>(8); paramMap.put("name","xiaoming"); paramMap.put("age","12"); String paramJsonStr = JSONObject.toJSONString(paramMap); // 實(shí)際參數(shù) url = "http://localhost:8080/api/user?user={\"name\":\"xiaoming\",\"age\":\"12\"}&country=china"; String url = "http://localhost:8080/api/user?user=" + paramJsonStr + "&country=china"; RestTemplate restTemplate = new RestTemplate(); // 調(diào)用出錯(cuò) Object execute = restTemplate.execute(url, HttpMethod.GET, null, null);
此時(shí)當(dāng)我們運(yùn)行程序時(shí)會(huì)拋出以下錯(cuò)誤:
錯(cuò)誤意思大概是沒(méi)有足夠可用的變量值來(lái)填充擴(kuò)展 'name',這是什么鬼意思,別著急讓我們跟跟代碼看看異常拋出的位置,最終定位如下,在創(chuàng)建URI過(guò)程中調(diào)用了 UriComponents.expandUriComponent()方法拋出異常:
這段代碼的作用其實(shí)就是通過(guò)NAMES_PATERN規(guī)則匹配到相應(yīng)字符串然后利用 uriVariables.getValue(varibaleName)進(jìn)行替換,再看看NAMES_PATERN的值就是用來(lái)匹配{}中的字符串內(nèi)容的
private static final Pattern NAMES_PATTERN = Pattern.compile(\\{([^/]+?)\\});
問(wèn)題分析到這兒相信大家應(yīng)該也明白了RestTemplate在創(chuàng)建URI時(shí)會(huì)進(jìn)行{param}占位替換,這個(gè)規(guī)則在文本輸出時(shí)應(yīng)用比較多,如日志打印和控制臺(tái)打印中常有使用:
String value = "test"; logger.info("占位參數(shù){}",value);
解決辦法:
找到原因了,那么我們應(yīng)當(dāng)如何解決呢,既然RestTemplate在處理url時(shí)會(huì)進(jìn)行{}變量替換,那它理應(yīng)提供相應(yīng)的接口調(diào)用,查看RestTemplate源碼它提供了多個(gè)exchange重載方法,其中多個(gè)方法都有uriVariables參數(shù),如下所示:
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException { URI expanded = this.getUriTemplateHandler().expand(url, uriVariables); return this.doExecute(expanded, method, requestCallback, responseExtractor); }
那么我們將url稍微修改即可解決問(wèn)題:
2. 慎!【url參數(shù)中有經(jīng)過(guò)URLEncode的字符串】
其實(shí)在遇到第一個(gè)坑時(shí),我并沒(méi)有采用上面給出的解決方式,而是想著將Json字符串經(jīng)過(guò)URLEncode編碼后在拼接到url后面,不就沒(méi)有{}符號(hào)了,不就可以完美解決問(wèn)題了,心里想著就美滋滋,那讓我們來(lái)試一把吧:
// JSON參數(shù) Map<String, String> paramMap = new HashMap<>(8); paramMap.put("name","xiaoming"); paramMap.put("age","12"); String paramJsonStr = JSONObject.toJSONString(paramMap); // 實(shí)際參數(shù) url = "http://localhost:8080/api/user?user=%7B%22name%22%3A%22xiaoming%22%2C%22age%22%3A%2212%22%7D&country=china"; String encode = URLEncoder.encode(paramJsonStr, "utf-8"); String url = "http://localhost:8080/api/user?user="+encode+"&country=china"; System.out.println(url); RestTemplate restTemplate = new RestTemplate(); Object execute = restTemplate.execute(url, HttpMethod.GET, null, null,paramJsonStr);
json字符串經(jīng)過(guò)編碼后已經(jīng)沒(méi)有{}符號(hào)了,也能夠成功調(diào)用接口,但是接口提供方無(wú)情的返回了錯(cuò)誤:參數(shù)無(wú)法反序列化。聽這口氣肯定也是這json串的原因,趕緊用Postman試一試:
神奇的一幕出現(xiàn)了,居然成功了! Postman方式調(diào)用和RestTemplate調(diào)用有什么不一樣,為什么postman行,restTemplate不行?趕緊查看服務(wù)器日志看看兩種方式接收到的參數(shù)有和不一樣:
1. Postman方式服務(wù)器接收到的url:
"http://localhost:8080/api/user?user=%7B%22name%22%3A%22xiaoming%22%2C%22age%22%3A%2212%22%7D&country=china"
2. RestTemplate方式服務(wù)器接收到的url
"http://localhost:8080/api/user?user=%257B%2522name%2522%3A%2522xiaoming%2522%2C%2522age%2522%253A%252212%2522%257D&country=china"
restTemplate居然在每個(gè)百分號(hào)%后面都擅自加了25這個(gè)數(shù)字,難怪服務(wù)端沒(méi)法解析,它為什么要這么做?難道是restTemplate的url處理bug?讓我們跟一跟代碼看個(gè)究竟:
詳細(xì)調(diào)用層次就不貼了,簡(jiǎn)單來(lái)說(shuō)就是RestTemplate在處理url時(shí)會(huì)對(duì)url參數(shù)進(jìn)行再編碼,也就是會(huì)對(duì)url中的特殊字符進(jìn)行轉(zhuǎn)義,如%號(hào)會(huì)被轉(zhuǎn)義為%25,所以傳給服務(wù)端的url就被改變了,具體url特殊字符轉(zhuǎn)義知識(shí)請(qǐng)查看這篇文章
既然知道了原因,那么我們應(yīng)該如何解決呢?
解決方案:
RestTemplate中的URI對(duì)象是通過(guò)UriTemplateHandler生成的,所以我們只需要利用java.net包中的URI自己構(gòu)建URI對(duì)象傳給RestTemplate即可,這樣url中的特殊字符就不會(huì)被轉(zhuǎn)義了:
3. 慎!【url參數(shù)中存在特殊字符】 --- 針對(duì)HttpClient
前面兩個(gè)坑然我對(duì)RestTemplate有點(diǎn)望而生畏了,既然RestTemplate這么多坑,那好咋們換回老家伙apache家族的HttpClient,本以為可以一切順利,沒(méi)成想一坑接著一坑啊!!!
先看看調(diào)用場(chǎng)景:按照出生時(shí)間去查詢用戶信息
調(diào)用請(qǐng)求都還沒(méi)發(fā)出就無(wú)情報(bào)錯(cuò)了:
通過(guò)異常信息可以容易知道在創(chuàng)建URL對(duì)象時(shí)url參數(shù)索引位置50處有非法字符,而這個(gè)字符剛好就是時(shí)間參數(shù)中的空格!,跟蹤代碼異常發(fā)生位置發(fā)現(xiàn)下面一段注釋,大意就是不允許url中有特殊字符存在,看了本文第二個(gè)坑的應(yīng)該已經(jīng)明白u(yù)rl中特殊字符為什么需要進(jìn)行轉(zhuǎn)義了,這里就不詳細(xì)敘述,至此解決方案就呼之欲出了。
解決方案:
需要對(duì)參數(shù)中的特殊字符進(jìn)行轉(zhuǎn)義:
1. 直接特殊字符替換
2. 利用google的工具包UrlEscapers(可以處理url、xml、html中的特殊字符)
maven依賴
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.1-jre</version> </dependency>
使用方式:UrlEscapers可以對(duì)路徑、參數(shù)、片段進(jìn)行處理,提供了 path,parameter,fragment三個(gè)部分的Escape實(shí)例
分別調(diào)用UrlEscapers類的以下方法獲?。?/p>
urlFormParameterEscaper()
urlPathSegmentEscaper()
urlFragmentEscaper()
總結(jié)
本次三個(gè)案例本人覺(jué)得還是具有典型性,由于平時(shí)發(fā)起請(qǐng)求大多通過(guò)瀏覽器或者是postman這類的http模擬工具進(jìn)行,而瀏覽器和模擬工具在內(nèi)部會(huì)對(duì)請(qǐng)求url和參數(shù)進(jìn)行一定處理,例如編碼和轉(zhuǎn)義,所以平時(shí)對(duì)這塊關(guān)注不多,而當(dāng)我們?cè)趕erver端自我構(gòu)建http請(qǐng)求進(jìn)行遠(yuǎn)程調(diào)用時(shí)這類問(wèn)題就需要我們特別注意,稍有不慎就會(huì)掉入坑中,還有一點(diǎn)感悟在使用一個(gè)工具類時(shí)應(yīng)當(dāng)先大致閱讀一下工具類提供了哪些方法,做到心中有數(shù)使用時(shí)就會(huì)少走很多彎路。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
IDEA 如何導(dǎo)入別人的javaweb項(xiàng)目進(jìn)行部署
這篇文章主要介紹了IDEA 如何導(dǎo)入別人的javaweb項(xiàng)目進(jìn)行部署,本文給大家分享我的詳細(xì)部署過(guò)程及遇到問(wèn)題解決方法,需要的朋友可以參考下2023-03-03springcloud gateway自定義斷言規(guī)則詳解,以后綴結(jié)尾進(jìn)行路由
這篇文章主要介紹了springcloud gateway自定義斷言規(guī)則詳解,以后綴結(jié)尾進(jìn)行路由,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10SpringBoot結(jié)合JSR303對(duì)前端數(shù)據(jù)進(jìn)行校驗(yàn)的示例代碼
這篇文章主要介紹了SpringBoot結(jié)合JSR303對(duì)前端數(shù)據(jù)進(jìn)行校驗(yàn)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java中轉(zhuǎn)換器設(shè)計(jì)模式深入講解
這篇文章主要給大家介紹了關(guān)于Java中轉(zhuǎn)換器設(shè)計(jì)模式的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04