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

原文鏈接:https://blog.csdn.net/qq_27529917/article/details/81064820

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( ) 得到的對象,這篇博客主要就是講解此方法的執行過程。當然直接貼源碼那太無腦了,我主要是會將解析的過程總結成一個個點,以讓大家明白在使用過程中需要注意以及可以靈活拓展的地方。

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

以上就是解析@FeignClient接口的,生成相應接口的動態代理對象的過程。最終所有信息都彙總到SynchronousMethodHandler對象裏,在實際執行Http請求時,根據接口上的參數數據和MethodHandler信息生成feign.Request對象,此對象裏裝着當前Http請求的所有信息,然後Feign將這些信息拷貝到RestTemplate中,就能執行相應的Http請求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章