背景
業(yè)務(wù)方經(jīng)過某些業(yè)務(wù)處理之后寥袭,帶過來了一系列參數(shù)。其中关霸,有個(gè)url參數(shù)供回調(diào)使用传黄,但這個(gè)url經(jīng)業(yè)務(wù)方處理后很特殊,帶過來回調(diào)時(shí)頻頻出錯(cuò)队寇,無法正潮礻回調(diào)給業(yè)務(wù)方。于是英上,通過分析部分源碼炭序,剖析一下回調(diào)的流程啤覆。
編碼 解碼
說到url的回調(diào)苍日,很容易聯(lián)想到decode、encode窗声,即編碼解碼操作相恃。
通常來說,需要編碼操作即意味著不適合直接傳輸笨觅,某些字符可能有歧義拦耐,如中文、特殊字符等等见剩。
而通過編碼之后(如 utf-8)杀糯,傳輸給服務(wù)端,服務(wù)端自行解碼苍苞,便不會(huì)出現(xiàn)一系列亂碼問題固翰。
說明
按照如上所說狼纬,業(yè)務(wù)方帶過來了處理后的url,另附上說明骂际,url無需解碼疗琉,直接回調(diào)即可,但事情往往沒那么順利歉铝。不管是使用spring5 webclient盈简,還是restTemplate以get請(qǐng)求回調(diào)業(yè)務(wù)方,均會(huì)出錯(cuò)太示。由于最終使用了restTemplate解決了問題柠贤,對(duì)此本次剖析一下restTemplate。
注:WebClient同樣可以解決問題类缤,剖析restTemplate的因由在于綜合分析了項(xiàng)目的引用和耦合度种吸,不適合改動(dòng)webclient模塊,故而拓展了restTemplate呀非。
剖析
-
先寫個(gè)極其簡(jiǎn)單的單測(cè)入口坚俗,進(jìn)行RestTemplate的源碼分析。
- 緊接著集中分析RestTemplate類
-
請(qǐng)求頭岸裙、請(qǐng)求體是由RequestCallback來處理的猖败,請(qǐng)求結(jié)果是由ResponseExtractor來處理。
-
RestTemplate默認(rèn)已經(jīng)初始化添加了許多對(duì)象讀寫轉(zhuǎn)換器降允。
-
進(jìn)入execute這個(gè)重要的方法繼續(xù)分析恩闻。
- 可以看出,先用UriTemplateHandler構(gòu)建URI剧董,再走關(guān)鍵邏輯幢尚。
-
UriTemplateHandler build URI
-
通過構(gòu)造函數(shù)的初始化,可以看到使用的是默認(rèn)builder工廠
-
從類繼承圖可以看出DefaultUriBuilderFactory實(shí)現(xiàn)了UriBuilderFactory翅楼,繼承了UriTemplateHandler
-
expand(String var1, Object... var2)是UriTemplateHandler接口的方法
-
繼續(xù)分析UriTemplateHandler接口expand方法源碼
-
到這里我們可以發(fā)現(xiàn)DefaultUriBuilderFactory默認(rèn)builder工廠存在一個(gè)內(nèi)部類DefaultUriBuilder
-
接著進(jìn)入分析一下UriComponentsBuilder類的源碼尉剩,從類名可以猜想到這個(gè)UriComponentsBuilder構(gòu)建器可能用于創(chuàng)建UriComponents實(shí)例
-
顧名思義,UriComponents包含了構(gòu)成URI的組件毅臊,以及一系列的getter and setter方法
-
繼續(xù)著剛才初始化UriComponentsBuilder的方法
-
符合了內(nèi)置的url pattern之后繼續(xù)構(gòu)建UriComponentsBuilder理茎,通過構(gòu)造器可以看出默認(rèn)字符集是utf-8
-
可以清晰看出初始化之后的構(gòu)建器
-
接下來出來外層的包裹,分析build方法
-
分析DefaultUriBuilderFactory build的過程
劃重點(diǎn)9苕摇T砹帧!
因?yàn)閡riVariableValues為空蚯撩,不關(guān)心這個(gè)expand方法础倍,此時(shí)注意build方法。
-
可以看到胎挎,build UriComponents沟启,有個(gè)encoded參數(shù)扰楼,默認(rèn)為false
先不透露具體細(xì)節(jié),只需先記住構(gòu)建這次請(qǐng)求的UriComponents美浦,encoded參數(shù)為falseO依怠!浦辨!
接著看encoded參數(shù)為false蹬竖,會(huì)影響到哪些參數(shù)值
由encodedfalse可得到UriComponentsBuilder.EncodingHint為NONE
public UriComponents build(boolean encoded) {
return this.buildInternal(encoded ? UriComponentsBuilder.EncodingHint.FULLY_ENCODED : (this.encodeTemplate ? UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE : UriComponentsBuilder.EncodingHint.NONE));
}
-
再接著就是構(gòu)造HierarchicalUriComponents
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, hint == UriComponentsBuilder.EncodingHint.FULLY_ENCODED);
- 最后可以看到HierarchicalUriComponents.EncodeState為RAW
這里劃重點(diǎn)!A鞒辍币厕!
-
繼續(xù)看外層的包裹,構(gòu)建完之后的UriComponents
-
繼續(xù)分析createUri芽腾,由于匹配到是默認(rèn)的URI_COMPONENT旦装,則走encode方法
-
本次剖析很重要的一個(gè)方法,終于要接近問題的真相了L稀R蹙睢!
-
從上個(gè)提示重要的剖析點(diǎn)得知艰躺,構(gòu)建UriComponents呻袭,encoded參數(shù)默認(rèn)為false,進(jìn)而從源碼可得知腺兴,HierarchicalUriComponents.EncodeState的值為RAW左电。那么就會(huì)走下面的encodeUriComponent的方法(至于怎么encode暫不在分析范圍內(nèi),看源碼貌似是用字節(jié)流操作來encode页响,有興趣可以閱讀)
-
接著對(duì)segment篓足,queryParams等進(jìn)行編碼
-
參數(shù)編碼的細(xì)節(jié),勾出來的是Java8的Function闰蚕,實(shí)現(xiàn)就不貼了栈拖,源碼比較簡(jiǎn)單
-
最終,通過比對(duì)參數(shù)ext_info的值陪腌,便得知參數(shù)被主動(dòng)encode了
解決方案
ok~~~
分析完后辱魁,知道問題的根源在不該被encode的URL卻在restTemplate的深處被動(dòng)encode了烟瞧,因此導(dǎo)致了回調(diào)的失敗诗鸭。既然明確問題所在,也分析過源碼参滴,解決起來就很簡(jiǎn)單了强岸。如下:
構(gòu)建UriComponentsBuilder,builder構(gòu)建時(shí)傳入encoded值為true砾赔,并轉(zhuǎn)URI形式入?yún)Ⅱ蚬浚瑔栴}解決青灼。
枚舉狀態(tài)總結(jié)
為了幫助大家記憶,簡(jiǎn)單理一下狀態(tài)妓盲。
- UriComponentsBuilder.EncodingHint
UriComponentsBuilder構(gòu)建器有個(gè)枚舉EncodingHint杂拨,UriComponentsBuilder構(gòu)建時(shí)encoded默認(rèn)為false。
- 若encoded為false悯衬,則看有沒有提供另外的encodeTemplate弹沽;
若有則值為ENCODE_TEMPLATE,否則為NONE - 若encoded為true時(shí)筋粗,值為FULLY_ENCODED
- HierarchicalUriComponents.EncodeState
HierarchicalUriComponents組件有個(gè)枚舉EncodeState策橘,其值的影響在于以上的EncodingHint。
源碼的encode過濾也在于這個(gè)組件的EncodeState值D纫凇@鲆选!
- 若EncodingHint為FULLY_ENCODED买决,則EncodeState為FULLY_ENCODED
- 若EncodingHint不為FULLY_ENCODED(ENCODE_TEMPLATE或NONE)沛婴,則EncodeState為RAW
總結(jié)
- 多多閱讀源碼,只會(huì)使用還遠(yuǎn)遠(yuǎn)不夠督赤。
- 帶著問題閱讀源碼瘸味,是種不錯(cuò)的學(xué)習(xí)方式。