成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

33張圖探秘OpenFeign核心架構原理

開發 架構
當調用動態代理方法的時候,Feign就會將上述解析出來的Http請求基本參數和方法入參組裝成一個Http請求,然后發送Http請求,獲取響應,再根據響應的內容的類型將響應體的內容轉換成對應的類型‘’這就是Feign的大致原理。

大家好,我是三友~~

在很久之前,我寫過兩篇關于OpenFeign和Ribbon這兩個SpringCloud核心組件架構原理的文章

但是說實話,從我現在的角度來看,這兩篇文章的結構和內容其實還可以更加完善

剛好我最近打算整個SpringCloud各個組件架構原理的小冊子

所以趁著這個機會,我就來重新寫一下這兩篇文章,彌補之前文章的不足

這一篇文章就先來講一講OpenFeign的核心架構原理

整篇文章大致分為以下四個部分的內容:

第一部分,脫離于SpringCloud,原始的Feign是什么樣的?

第二部分,Feign的核心組件有哪些,整個執行鏈路是什么樣的?

第三部分,SpringCloud是如何把Feign融入到自己的生態的?

第四部分,OpenFeign有幾種配置方式,各種配置方式的優先級是什么樣的?

好了,話不多說,接下來就直接進入主題,來探秘OpenFeign核心架構原理

原始Feign是什么樣的?

在日常開發中,使用Feign很簡單,就三步

第一步:引入依賴

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
     <version>2.2.5.RELEASE</version>
</dependency>

第二步:在啟動引導類加上@EnableFeignClients注解

@SpringBootApplication
@EnableFeignClients
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

第三步:寫個FeignClient接口

@FeignClient(name = "order")
@RequestMapping("/order")
public interface OrderApiClient {

    @GetMapping
    Order queryOrder(@RequestParam("orderId") Long orderId);

}

之后當我們要使用時,只需要注入OrderApiClient對象就可以了

雖然使用方便,但這并不是Feign最原始的使用方式,而是SpringCloud整合Feign之后的使用方式

Feign最開始是由Netflix開源的

后來SpringCloud就將Feign進行了一層封裝,整合到自己的生態,讓Feign使用起來更加簡單

并同時也給它起了一個更高級的名字,OpenFeign

接下來文章表述有時可能并沒有嚴格區分Feign和OpenFeign的含義,你知道是這么個意思就行了。

Feign本身有自己的使用方式,也有類似Spring MVC相關的注解,如下所示:

public interface OrderApiClient {

    @RequestLine("GET /order/{orderId}")
    Order queryOrder(@Param("orderId") Long orderId);

}

OrderApiClient對象需要手動通過Feign.builder()來創建

public class FeignDemo {

    public static void main(String[] args) {
        OrderApiClient orderApiClient = Feign.builder()
                .target(OrderApiClient.class, "http://localhost:8088");
        orderApiClient.queryOrder(9527L);
    }

}

Feign的本質:動態代理 + 七大核心組件

相信稍微了解Feign的小伙伴都知道,Feign底層其實是基于JDK動態代理來的

所以Feign.builder()最終構造的是一個代理對象

Feign在構建動態代理的時候,會去解析方法上的注解和參數

獲取Http請求需要用到基本參數以及和這些參數和方法參數的對應關系

比如Http請求的url、請求體是方法中的第幾個參數、請求頭是方法中的第幾個參數等等

之后在構建Http請求時,就知道請求路徑以及方法的第幾個參數對應是Http請求的哪部分數據

當調用動態代理方法的時候,Feign就會將上述解析出來的Http請求基本參數和方法入參組裝成一個Http請求

然后發送Http請求,獲取響應,再根據響應的內容的類型將響應體的內容轉換成對應的類型

這就是Feign的大致原理

圖片圖片

在整個Feign動態代理生成和調用過程中,需要依靠Feign的一些核心組件來協調完成

如下圖所示是Feign的一些核心組件

這些核心組件可以通過Feign.builder()進行替換

由于組件很多,這里我挑幾個重要的跟大家講一講

1、Contract

圖片圖片

前面在說Feign在構建動態代理的時候,會去解析方法上的注解和參數,獲取Http請求需要用到基本參數

而這個Contract接口的作用就是用來干解析這件事的

Contract的默認實現是解析Feign自己原生注解的

圖片圖片

解析時,會為每個方法生成一個MethodMetadata對象

圖片圖片

MethodMetadata就封裝了Http請求需要用到基本參數以及這些參數和方法參數的對應關系

SpringCloud在整合Feign的時候,為了讓Feign能夠識別Spring MVC的注解,所以就自己實現了Contract接口

2、Encoder

通過名字也可以看出來,這個其實用來編碼的

具體的作用就是將請求體對應的方法參數序列化成字節數組

Feign默認的Encoder實現只支持請求體對應的方法參數類型為String和字節數組

圖片圖片

如果是其它類型,比如說請求體對應的方法參數類型為AddOrderRequest.class類型,此時就無法對AddOrderRequest對象進行序列化

這就導致默認情況下,這個Encoder的實現很難用

于是乎,Spring就實現了Encoder接口

圖片圖片

可以將任意請求體對應的方法參數類型對象序列化成字節數組

3、Decoder

Decoder的作用恰恰是跟Encoder相反

Encoder是將請求體對應的方法參數序列化成字節數組

而Decoder其實就是將響應體由字節流反序列化成方法返回值類型的對象

Decoder默認情況下跟Encoder的默認情況是一樣的,只支持反序列化成字節數組或者是String

所以,Spring也同樣實現了Decoder,擴展它的功能

圖片圖片

可以將響應體對應的字節流反序列化成任意返回值類型對象

4、Client

圖片

從接口方法的參數和返回值其實可以看出,這其實就是動態代理對象最終用來執行Http請求的組件

默認實現就是通過JDK提供的HttpURLConnection來的

除了這個默認的,Feign還提供了基于HttpClient和OkHttp實現的

在項目中,要想替換默認的實現,只需要引入相應的依賴,在構建Feign.builder()時設置一下就行了

SpringCloud環境底下會根據引入的依賴自動進行設置

除了上述的三個實現,最最重要的當然是屬于它基于負載均衡的實現

如下是OpenFeign用來整合Ribbon的核心實現

圖片圖片

這個Client會根據服務名,從Ribbon中獲取一個服務實例的信息,也就是ip和端口

之后會通過ip和端口向服務實例發送Http請求

5、InvocationHandlerFactory

InvocationHandler我相信大家應該都不陌生

對于JDK動態代理來說,必須得實現InvocationHandler才能創建動態代理

InvocationHandler的invoke方法實現就是動態代理走的核心邏輯

而InvocationHandlerFactory其實就是創建InvocationHandler的工廠

所以,這里就可以猜到,通過InvocationHandlerFactory創建的InvocationHandler應該就是Feign動態代理執行的核心邏輯

InvocationHandlerFactory默認實現是下面這個

SpringCloud環境下默認也是使用它的這個默認實現

所以,我們直接去看看InvocationHandler的實現類FeignInvocationHandler

圖片圖片

從實現可以看出,除了Object類的一些方法,最終會調用方法對應的MethodHandler的invoke方法

所以注意注意,這個MethodHandler就封裝了Feign執行Http調用的核心邏輯,很重要,后面還會提到

圖片圖片

雖然說默認情況下SpringCloud使用是默認實現,最終使用FeignInvocationHandler

但是當其它框架整合SpringCloud生態的時候,為了適配OpenFeign,有時會自己實現InvocationHandler

比如常見的限流熔斷框架Hystrix和Sentinel都實現了自己的InvocationHandler

這樣就可以對MethodHandler執行前后,也就是Http接口調用前后進行限流降級等操作。

6、RequestInterceptor

圖片圖片

RequestInterceptor它其實是一個在發送請求前的一個攔截接口

通過這個接口,在發送Http請求之前再對Http請求的內容進行修改

比如我們可以設置一些接口需要的公共參數,如鑒權token之類的

@Component
public class TokenRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        template.header("token", "token值");
    }

}

7、Retryer

這是一個重試的組件,默認實現如下

默認情況下,最大重試5次

在SpringCloud下,并沒有使用上面那個實現,而使用的是下面這個實現

圖片圖片

所以,SpringCloud下默認是不會進行重試

小總結

這一節主要是介紹了7個Feign的核心組件以及Spring對應的擴展實現

為了方便你查看,我整理了如下表格

接口

作用

Feign默認實現

Spring實現

Contract

解析方法注解和參數,將Http請求參數和方法參數對應

Contract.Default

SpringMvcContract

Encoder

將請求體對應的方法參數序列化成字節數組

Encoder.Default

SpringEncoder

Decoder

將響應體的字節流反序列化成方法返回值類型對象

Decoder.Default

SpringDecoder

Client

發送Http請求

Client.Default

LoadBalancerFeignClient

InvocationHandlerFactory

InvocationHandler工廠,動態代理核心邏輯

InvocationHandlerFactory.Default

RequestInterceptor

在發送Http請求之前,再對Http請求的內容進行攔截修改

Retryer

重試組件

Retryer.Default

除了這些之外,還有一些其它組件這里就沒有說了

比如日志級別Logger.Level,日志輸出Logger,有興趣的可以自己查看

Feign核心運行原理分析

上一節說了Feign核心組件,這一節我們來講一講Feign核心運行原理,主要分為兩部分內容:

  • 動態代理生成原理
  • 一次Feign的Http調用執行過程

1、動態代理生成原理

這里我先把上面的Feign原始使用方式的Demo代碼再拿過來

public class FeignDemo {

    public static void main(String[] args) {
        OrderApiClient orderApiClient = Feign.builder()
                .target(OrderApiClient.class, "http://localhost:8088");
        orderApiClient.queryOrder(9527L);
    }

}

通過Demo可以看出,最后是通過Feign.builder().target(xx)獲取到動態代理的

而上述代碼執行邏輯如下所示:

圖片圖片

最終會調用ReflectiveFeign的newInstance方法來創建動態代理對象

而ReflectiveFeign內部設置了前面提到的一些核心組件

接下我們來看看newInstance方法

這個方法主要就干兩件事:

第一件事首先解析接口,構建每個方法對應的MethodHandler

MethodHandler在前面講InvocationHandlerFactory特地提醒過

動態代理(FeignInvocationHandler)最終會調用MethodHandler來處理Feign的一次Http調用

在解析接口的時候,就會用到前面提到的Contract來解析方法參數和注解,生成MethodMetadata,這里我代碼我就不貼了

第二件事通過InvocationHandlerFactory創建InvocationHandler

然后再構建出接口的動態代理對象

ok,到這其實就走完了動態代理的生成過程

所以動態代理生成邏輯很簡單,總共也沒幾行代碼,畫個圖來總結一下

圖片圖片

2、一次Feign的Http調用執行過程

前面說了,調用接口動態代理的方式時,通過InvocationHandler(FeignInvocationHandler),最終交給MethodHandler的invoke方法來執行

MethodHandler是一個接口,最終會走到它的實現類SynchronousMethodHandler的invoke方法實現

SynchronousMethodHandler中的屬性就是我們前面提到的一些組件

由于整個代碼調用執行鏈路比較長,這里我就不截代碼了,有興趣的可以自己翻翻

不過這里我畫了一張圖,可以通過這張圖來大致分析整個Feign一次Http調用的過程

圖片圖片

  • 首先就是前面說的,進入FeignInvocationHandler,找到方法對應的SynchronousMethodHandler,調用invoke方法實現
  • 之后根據MethodMetadata和方法的入參,構造出一個RequestTemplate,RequestTemplate封裝了Http請求的參數,在這個過程中,如果有請求體,那么會通過Encoder序列化
  • 然后調用RequestInterceptor,通過RequestInterceptor對RequestTemplate進行攔截擴展,可以對請求數據再進行修改
  • 再然后將RequestTemplate轉換成Request,Request其實跟RequestTemplate差不多,也是封裝了Http請求的參數
  • 接下來通過Client去根據Request中封裝的Http請求參數,發送Http請求,得到響應Response
  • 最后根據Decoder,將響應體反序列化成方法返回值類型對象,返回

這就是Feign一次Http調用的執行過程

如果有設置重試,那么也是在這個階段生效的

SpringCloud是如何整合Feign的?

SpringCloud在整合Feign的時候,主要是分為兩部分

  • 核心組件重新實現,支持更多SpringCloud生態相關的功能
  • 將接口動態代理對象注入到Spring容器中

第一部分核心組件重新實現前面已經都說過了,這里就不再重復了

至于第二部分我們就來好好講一講,Spring是如何將接口動態代理對象注入到Spring容器中的

1、將FeignClient接口注冊到Spring中

使用OpenFeign時,必須加上@EnableFeignClients

這個注解就是OpenFeign的發動機

圖片圖片

@EnableFeignClients最后通過@Import注解導入了一個FeignClientsRegistrar

圖片圖片

FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar

所以最終Spring在啟動的時候會調用registerBeanDefinitions方法實現

之所以會調用registerBeanDefinitions方法,是@Import注解的作用,不清楚的同學可以看一下扒一扒Bean注入到Spring的那些姿勢,你會幾種?

圖片圖片

最終會走到registerFeignClients這個方法

這個方法雖然比較長,主要是干了下面這個2件事:

第一件事,掃描@EnableFeignClients所在類的包及其子包(如果有指定包就掃指定包),找出所有加了@FeignClient注解的接口,生成一堆BeanDefinition

這個BeanDefinition包含了這個接口的信息等信息

第二件事,將掃描到的這些接口注冊到Spring容器中

圖片圖片

在注冊的時候,并非直接注冊接口類型,而是FeignClientFactoryBean類型

圖片圖片

好了,到這整個@EnableFeignClients啟動過程就結束了

雖然上面寫的很長,但是整個@EnableFeignClients其實也就只干了一件核心的事

掃描到所有的加了@FeignClient注解的接口

然后為每個接口生成一個Bean類型為FeignClientFactoryBean的BeanDefinition

最終注冊到Spring容器中

圖片圖片

2、FeignClientFactoryBean的秘密

上一節說到,每個接口都對應一個class類型為FeignClientFactoryBean的BeanDefinition

圖片圖片

如上所示,FeignClientFactoryBean是一個FactoryBean

并且FeignClientFactoryBean的這些屬性,是在生成BeanDefinition的時候設置的

圖片圖片

并且這個type屬性就是代表的接口類型

由于實現了FactoryBean,所以Spring啟動過程中,一定為會調用getObject方法獲取真正的Bean對象

FactoryBean的作用就不說了,不清楚的小伙伴還是可以看看扒一扒Bean注入到Spring的那些姿勢,你會幾種?這篇文章

getObject最終會走到getTarget()方法

圖片圖片

從如上代碼其實可以看出來,最終還是會通過Feign.builder()來創建動態代理對象

只不過不同的是,SpringCloud會替換Feign默認的組件,改成自己實現的

總的來說,Spring是通過FactoryBean的這種方式,將Feign動態代理對象添加到Spring容器中

OpenFeign的各種配置方式以及對應優先級

既然Feign核心組件可以替換,那么在SpringCloud環境下,我們該如何去配置自己的組件呢?

不過在說配置之前,先說一下FeignClient配置隔離操作

在SpringCloud環境下,為了讓每個不同的FeignClient接口配置相互隔離

在應用啟動的時候,會為每個FeignClient接口創建一個Spring容器,接下來我就把這個容器稱為FeignClient容器

這些FeignClient的Spring容器有一個相同的父容器,那就是項目啟動時創建的容器

圖片圖片

SpringCloud會給每個FeignClient容器添加一個默認的配置類FeignClientsConfiguration配置類

圖片圖片

這個配置類就聲明了各種Feign的組件

圖片圖片

所以,默認情況下,OpenFeign就使用這些配置的組件構建代理對象

知道配置隔離之后,接下來看看具體的幾種方式配置以及它們之間的優先級關系

1、通過@EnableFeignClients注解的defaultConfiguration屬性配置

舉個例子,比如我自己手動聲明一個Contract對象,類型為MyContract

public class FeignConfiguration {
    
    @Bean
    public Contract contract(){
        return new MyContract();
    }
    
}

注意注意,這里FeignConfiguration我沒加@Configuration注解,原因后面再說

此時配置如下所示:

@EnableFeignClients(defaultConfiguration = FeignConfiguration.class)

之后這個配置類會被加到每個FeignClient容器中,所以這個配置是對所有的FeignClient生效

并且優先級大于默認配置的優先級

比如這個例子就會使得FeignClient使用我聲明的MyContract,而不是FeignClientsConfiguration中聲明的SpringMvcContract

2、通過@FeignClient注解的configuration屬性配置

還以上面的FeignConfiguration配置類舉例,可以通過@FeignClient注解配置

@FeignClient(name = "order", configuration = FeignConfiguration.class)

此時這個配置類會被加到自己FeignClient容器中,注意是自己FeignClient容器

所以這種配置的作用范圍是自己的這個FeignClient

并且這種配置的優先級是大于@EnableFeignClients注解配置的優先級

3、在項目啟動的容器中配置

前面提到,由于所有的FeignClient容器的父容器都是項目啟動的容器

所以可以將配置放在這個項目啟動的容器中

還以FeignConfiguration為例,加上@Configuration注解,讓項目啟動的容器的掃描到就成功配置了

這種配置的優先級大于前面提到的所有配置優先級

并且是對所有的FeignClient生效

所以,這就是為什么使用注解配置時為什么配置類不能加@Configuration注解的原因,因為一旦被項目啟動的容器掃描到,這個配置就會作用于所有的FeignClient,并且優先級是最高的,就會導致你其它的配置失效,當然你也可以加@Configuration注解,但是一定不能被項目啟動的容器掃到

4、配置文件

除了上面3種編碼方式配置,OpenFeign也是支持通過配置文件的方式進行配置

并且也同時支持對所有FeignClient生效和對單獨某個FeignClient生效

對所有FeignClient生效配置:

feign:
  client:
    config:
      default: # default 代表對全局生效
        contract: com.sanyou.feign.MyContract

對單獨某個FeignClient生效配置:

feign:
  client:
    config:
      order: # 具體的服務名
        contract: com.sanyou.feign.MyContract

在默認情況下,這種配置文件方式優先級最高

但是如果你在配置文件中將配置項feign.client.default-to-properties設置成false的話,配置文件的方式優先級就是最低了

feign:
  client:
    default-to-properties: false

小總結

這一節,總共總結了4種配置OpenFeign的方式以及它們優先級和作用范圍

畫張圖來總結一下

圖片圖片

如果你在具體使用的時候,還是遇到了一些優先級的問題,可以debug這部分源碼,看看到底生效的是哪個配置


圖片圖片

圖片

責任編輯:武曉燕 來源: 三友的java日記
相關推薦

2023-10-16 22:07:20

Spring配置中心Bean

2024-01-02 22:47:47

Nacos注冊中心節點

2023-11-30 22:06:43

2024-07-08 23:03:13

2022-01-14 12:28:18

架構OpenFeign遠程

2010-08-18 10:13:55

IntentAndroid

2009-06-15 15:57:21

Spring工作原理

2020-12-04 06:37:19

HTTPS原理安全

2025-01-10 09:47:43

blockSDKiOS

2009-08-25 13:48:01

Java EE架構企業級應用

2024-02-26 00:00:00

Nginx服務器HTTP

2023-12-05 17:44:24

reactor網絡

2010-03-12 17:09:18

2010-01-27 17:38:58

Windows Emb

2019-12-12 10:56:00

微服務微服務架構架構

2023-06-07 15:25:19

Kafka版本日志

2025-02-08 08:10:00

2009-06-10 09:21:45

Google Wave架構

2024-08-07 08:19:13

2022-01-05 14:30:44

容器Linux網絡
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: h片在线免费观看 | 精品久久99 | 亚洲欧美一区二区在线观看 | 国产精品网址 | 婷婷五月色综合香五月 | 日韩成人性视频 | 精品成人在线 | 国产精品18毛片一区二区 | 伊人狠狠操 | 国产视频久 | 国产精品亚洲精品日韩已方 | 狠狠狠色丁香婷婷综合久久五月 | 亚洲日韩中文字幕一区 | 色吊丝2288sds中文字幕 | 欧美在线色| 国产精品揄拍一区二区久久国内亚洲精 | 国产亚洲一区精品 | 最新一级毛片 | 久久精品亚洲欧美日韩精品中文字幕 | 国产成人免费 | 久久亚洲美女 | 成人高清在线 | 一区二区蜜桃 | 午夜免费成人 | 国产日韩欧美二区 | 国产成人99久久亚洲综合精品 | 日韩三级在线 | 成人免费视频网 | 日韩一级免费观看 | 日日操操| 国产黄色大片 | 美国十次成人欧美色导视频 | 久久免费国产 | 高清免费在线 | 欧美日韩在线观看一区二区三区 | 在线成人福利 | 国产在线精品一区二区三区 | 亚洲精品9999久久久久 | 欧美一区二区三区在线 | 超碰国产在线 | 91精品国产综合久久久密闭 |