RestTemplate源碼剖析:Encode

背景

業(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。

剖析

  1. 先寫個(gè)極其簡單的單測入口,進(jìn)行RestTemplate的源碼分析。


  1. 緊接著集中分析RestTemplate類
  • 請(qǐng)求頭、請(qǐng)求體是由RequestCallback來處理的,請(qǐng)求結(jié)果是由ResponseExtractor來處理。


  • RestTemplate默認(rèn)已經(jīng)初始化添加了許多對(duì)象讀寫轉(zhuǎn)換器。


  1. 進(jìn)入execute這個(gè)重要的方法繼續(xù)分析。


  1. 可以看出,先用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接口的方法


  1. 繼續(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)建器


  1. 接下來出來外層的包裹,分析build方法


  • 分析DefaultUriBuilderFactory build的過程
    劃重點(diǎn)?。?!
    因?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ù)為false!??!

  • 接著看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)?。?!
  1. 繼續(xù)看外層的包裹,構(gòu)建完之后的UriComponents


  • 繼續(xù)分析createUri,由于匹配到是默認(rèn)的URI_COMPONENT,則走encode方法


  • 本次剖析很重要的一個(gè)方法,終于要接近問題的真相了?。?!

  • 從上個(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)就不貼了,源碼比較簡單


  • 最終,通過比對(duì)參數(shù)ext_info的值,便得知參數(shù)被主動(dòng)encode了


解決方案

ok~~~
分析完后,知道問題的根源在不該被encode的URL卻在restTemplate的深處被動(dòng)encode了,因此導(dǎo)致了回調(diào)的失敗。既然明確問題所在,也分析過源碼,解決起來就很簡單了。如下:

構(gòu)建UriComponentsBuilder,builder構(gòu)建時(shí)傳入encoded值為true,并轉(zhuǎn)URI形式入?yún)?,問題解決。


枚舉狀態(tài)總結(jié)

為了幫助大家記憶,簡單理一下狀態(tài)。

  1. 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
  1. HierarchicalUriComponents.EncodeState

HierarchicalUriComponents組件有個(gè)枚舉EncodeState,其值的影響在于以上的EncodingHint。
源碼的encode過濾也在于這個(gè)組件的EncodeState值?。?!

  • 若EncodingHint為FULLY_ENCODED,則EncodeState為FULLY_ENCODED
  • 若EncodingHint不為FULLY_ENCODED(ENCODE_TEMPLATE或NONE),則EncodeState為RAW

總結(jié)

  1. 多多閱讀源碼,只會(huì)使用還遠(yuǎn)遠(yuǎn)不夠。
  2. 帶著問題閱讀源碼,是種不錯(cuò)的學(xué)習(xí)方式。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容