SpringCloud全面解析@FeignClient標識接口的過程

Feign的作用

是將Http請求抽象化為一個Interface客戶端,可以調用接口的形式來執行Http請求,以達到簡化Http調用的目的。

Feign將分散在@FeignClient,@EnableFeignClients,標識接口,接口方法,Spring環境上的各種配置信息提取出來封裝成一個對象,然後將對象裡的信息註入到RestTemplate中,生成一次Http請求,然後執行。

正常在SpringMVC的Controller

是將Http請求的信息提取出來註入@RequestMapping標識的方法中;而Feign是將接口中的信息提取出來,封裝成一個Http請求的相關信息,是對SpringMVC解析過程的一個逆向處理。

當我們通過IOC註入接口對象時,得到的肯定是此接口的實現類對象,這個對象應該就是SpringCloud通過動態代理生成的對象。對於接口對象生成動態代理對象,一般選用JDK的Proxy,這樣實現簡單且耦合性低,SpringCloud就是如此。

SpringCloud將@FeignClient標識的接口

註冊成一個 FeignClientFactoryBean 類型的Bean對象,我們通過IOC註入的是此Bean的 getObject( ) 得到的對象,這篇文章主要就是講解此方法的執行過程。

當然直接貼源碼那太無腦瞭,我主要是會將解析的過程總結成一個個點

讓大傢明白在使用過程中需要註意以及可以靈活拓展的地方

  • 在解析接口前,先加載SpringCloud的Feign配置,默認情況先加載 @FeignClient,@EnableFeignClients註解上的配置,其次加載 Spring環境裡 feign.client.default 指定的配置,最後加載 feign.client.appName(應用名稱) 指定的配置後續的配置信息會覆蓋之前的,也就是越靠後的優先級越高。可以通過 “feign.client.defaultToProperties” 屬性來改變這種優先級順序
  • 驗證:接口方法參數長度不能為0
  • 驗證:標識@FeignClient的接口最多隻能繼承一個接口
  • 驗證:@FeignClient標識的接口的父接口不能再繼承自其它接口,也就是@FeignClient的接口最多也隻能有一個上級接口
  • 解析接口方法時, 忽略這些類型的方法:Object的方法,Static方法,Default方法
  • 將@FeignClient標識接口的最上級 Interface 的@RequestMapping註解的 value()值 設置為 MethodMetadata裡的RequestTemplate的 “url” 的第一位;也就是說如果@FeignClient標識接口 有Super Interface,那麼取Super Interface 的@RequestMapping;如果沒有,那麼取自己的@RequestMapping;如果都沒有此註解,那麼忽略
  • 如果 Method 上未標識 @RequestMapping,忽略
  • Method上的@RequestMapping ,其 method()和value()的值都最多隻能有一個
  • 將Method上 @RequestMapping 的 value() 追加到 RequestTemplate的 “url” 上,接在 接口上的 @RequestMapping 的value() 之後,機制匹配Controller。
  • 解析Method上 @RequestMapping 上的 produces(),驗證其值隻能有一個元素,將其值添加到Header上的 Accept 中,比如 Accept=application/json
  • 解析 Method上@RequestMapping 上的 consumes(),驗證其值隻能有一個元素,將其值添加到Header上的 Content-Type中,比如 Content-Type=application/json
  • 解析 Method上@RequestMapping 上的 headers(),headers是一個String[],其元素是一個個的鍵值對,value可以使用 "${ }"來獲取環境變量的值,比如userName=${spring.application.name}
  • 解析參數上的 @PathVariable 註解,如果 此註解上的 value()= id ,若 “{ id }” 不存在與 url , headers,queries中,那麼將 “id” 加入MethodMetadata 的 formParams 屬性中,一般不容易出現這種情況
  • 解析參數上的 @RequestHeader 註解,如果此參數類型是Map,設置下MethodMetadata 裡的headerMapIndex,也就是參數序號;如果不是,假設value()= uname,那麼將uname 和 參數值 作為鍵值對 加入到MethodMetadata 的 template(RequestTemplate ) 的 headers 屬性中
  • 解析參數上的 @RequestParam註解, 如果此參數類型是Map, 設置下MethodMetadata 裡的queryMapIndex, 也就是參數序號;如果不是,假設value()= uname,那麼將uname 和 參數值 作為鍵值對 加入到MethodMetadata 的 template(RequestTemplate ) 的 queries屬性中
  • @PathVariable,@RequestHeader, @RequestParam 這三個註解起作用的前提是SpringMVC裡有他們的轉換器,能夠將他們轉換為String。也就是說比如參數類型是Date,需要自定義一個Date > String 的轉換器,註入到ConversionService裡;其他復雜類型也可以自定義相應的轉換器。也就是這三個註解不是隻能標識基本數據類型,隻要定義瞭相應的轉化器,也可以標識復雜類型。
  • 一個參數可以標識以上3種註解,不同的註解執行時起不同的作用。不同的註解可能value()不同,也就是一個參數可能被放進多個地方,比如 ( @PathVariable(“name”) @RequestHeader(“id”) @RequestParam (“flag”) String userName ) 。每一個註解都會將此參數順序和value() 存入 MethodMetadata 的 indexToName,以備後續執行時解析。
  • 隻要參數標識上述3個註解中的一個,那麼將參數序號和轉換器放入 MethodMetadata 的 indexToExpander 中;多種註解共用一個轉換器,類型是 ConvertingExpander,也就是要將參數轉化為Http請求中的數據。
  • 如果參數上未標識上述3種註解,那麼此參數作為 RequestBody 的內容。一個方法中隻能有一個未標識註解的參數,將參數的序號和實際類型放入 MethodMetadata 的 bodyIndex 和 bodyType 中。
  • 將MethodMetadata(接口Class和方法上的數據),@FeignClient註解裡的數據,Spring環境裡配置的數據都放進 SynchronousMethodHandler 類型的對象中, 此對象將配合JDK的AOP動態代理,代理對象執行相應方法時將其轉發給SynchronousMethodHandler 執行,每一個Method對應一個SynchronousMethodHandler
  • 為@FeignClient標識的接口創建JDK動態代理對象,InvocationHandler類型為 :FeignInvocationHandler,持有Map< Method, MethodHandler > 類型的屬性,在調用相應方法時轉發給指定的 MethodHandler 處理。

以上就是解析@FeignClient接口的,生成相應接口的動態代理對象的過程。

最終所有信息都匯總到SynchronousMethodHandler對象裡,在實際執行Http請求時,根據接口上的參數數據和MethodHandler信息生成feign.Request對象,此對象裡裝著當前Http請求的所有信息,然後Feign將這些信息拷貝到RestTemplate中,就能執行相應的Http請求。

希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: