自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

33張圖探秘OpenFeign核心架構(gòu)原理

開發(fā) 架構(gòu)
當(dāng)調(diào)用動態(tài)代理方法的時候,F(xiàn)eign就會將上述解析出來的Http請求基本參數(shù)和方法入?yún)⒔M裝成一個Http請求,然后發(fā)送Http請求,獲取響應(yīng),再根據(jù)響應(yīng)的內(nèi)容的類型將響應(yīng)體的內(nèi)容轉(zhuǎn)換成對應(yīng)的類型‘’這就是Feign的大致原理。

大家好,我是三友~~

在很久之前,我寫過兩篇關(guān)于OpenFeign和Ribbon這兩個SpringCloud核心組件架構(gòu)原理的文章

但是說實話,從我現(xiàn)在的角度來看,這兩篇文章的結(jié)構(gòu)和內(nèi)容其實還可以更加完善

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

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

這一篇文章就先來講一講OpenFeign的核心架構(gòu)原理

整篇文章大致分為以下四個部分的內(nèi)容:

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

第二部分,F(xiàn)eign的核心組件有哪些,整個執(zhí)行鏈路是什么樣的?

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

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

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

原始Feign是什么樣的?

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

第一步:引入依賴

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

第二步:在啟動引導(dǎo)類加上@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);

}

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

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

Feign最開始是由Netflix開源的

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

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

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

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

public interface OrderApiClient {

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

}

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

public class FeignDemo {

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

}

Feign的本質(zhì):動態(tài)代理 + 七大核心組件

相信稍微了解Feign的小伙伴都知道,F(xiàn)eign底層其實是基于JDK動態(tài)代理來的

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

Feign在構(gòu)建動態(tài)代理的時候,會去解析方法上的注解和參數(shù)

獲取Http請求需要用到基本參數(shù)以及和這些參數(shù)和方法參數(shù)的對應(yīng)關(guān)系

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

之后在構(gòu)建Http請求時,就知道請求路徑以及方法的第幾個參數(shù)對應(yīng)是Http請求的哪部分?jǐn)?shù)據(jù)

當(dāng)調(diào)用動態(tài)代理方法的時候,F(xiàn)eign就會將上述解析出來的Http請求基本參數(shù)和方法入?yún)⒔M裝成一個Http請求

然后發(fā)送Http請求,獲取響應(yīng),再根據(jù)響應(yīng)的內(nèi)容的類型將響應(yīng)體的內(nèi)容轉(zhuǎn)換成對應(yīng)的類型

這就是Feign的大致原理

圖片圖片

在整個Feign動態(tài)代理生成和調(diào)用過程中,需要依靠Feign的一些核心組件來協(xié)調(diào)完成

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

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

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

1、Contract

圖片圖片

前面在說Feign在構(gòu)建動態(tài)代理的時候,會去解析方法上的注解和參數(shù),獲取Http請求需要用到基本參數(shù)

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

Contract的默認(rèn)實現(xiàn)是解析Feign自己原生注解的

圖片圖片

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

圖片圖片

MethodMetadata就封裝了Http請求需要用到基本參數(shù)以及這些參數(shù)和方法參數(shù)的對應(yīng)關(guān)系

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

2、Encoder

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

具體的作用就是將請求體對應(yīng)的方法參數(shù)序列化成字節(jié)數(shù)組

Feign默認(rèn)的Encoder實現(xiàn)只支持請求體對應(yīng)的方法參數(shù)類型為String和字節(jié)數(shù)組

圖片圖片

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

這就導(dǎo)致默認(rèn)情況下,這個Encoder的實現(xiàn)很難用

于是乎,Spring就實現(xiàn)了Encoder接口

圖片圖片

可以將任意請求體對應(yīng)的方法參數(shù)類型對象序列化成字節(jié)數(shù)組

3、Decoder

Decoder的作用恰恰是跟Encoder相反

Encoder是將請求體對應(yīng)的方法參數(shù)序列化成字節(jié)數(shù)組

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

Decoder默認(rèn)情況下跟Encoder的默認(rèn)情況是一樣的,只支持反序列化成字節(jié)數(shù)組或者是String

所以,Spring也同樣實現(xiàn)了Decoder,擴展它的功能

圖片圖片

可以將響應(yīng)體對應(yīng)的字節(jié)流反序列化成任意返回值類型對象

4、Client

圖片

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

默認(rèn)實現(xiàn)就是通過JDK提供的HttpURLConnection來的

除了這個默認(rèn)的,F(xiàn)eign還提供了基于HttpClient和OkHttp實現(xiàn)的

在項目中,要想替換默認(rèn)的實現(xiàn),只需要引入相應(yīng)的依賴,在構(gòu)建Feign.builder()時設(shè)置一下就行了

SpringCloud環(huán)境底下會根據(jù)引入的依賴自動進行設(shè)置

除了上述的三個實現(xiàn),最最重要的當(dāng)然是屬于它基于負(fù)載均衡的實現(xiàn)

如下是OpenFeign用來整合Ribbon的核心實現(xiàn)

圖片圖片

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

之后會通過ip和端口向服務(wù)實例發(fā)送Http請求

5、InvocationHandlerFactory

InvocationHandler我相信大家應(yīng)該都不陌生

對于JDK動態(tài)代理來說,必須得實現(xiàn)InvocationHandler才能創(chuàng)建動態(tài)代理

InvocationHandler的invoke方法實現(xiàn)就是動態(tài)代理走的核心邏輯

而InvocationHandlerFactory其實就是創(chuàng)建InvocationHandler的工廠

所以,這里就可以猜到,通過InvocationHandlerFactory創(chuàng)建的InvocationHandler應(yīng)該就是Feign動態(tài)代理執(zhí)行的核心邏輯

InvocationHandlerFactory默認(rèn)實現(xiàn)是下面這個

SpringCloud環(huán)境下默認(rèn)也是使用它的這個默認(rèn)實現(xiàn)

所以,我們直接去看看InvocationHandler的實現(xiàn)類FeignInvocationHandler

圖片圖片

從實現(xiàn)可以看出,除了Object類的一些方法,最終會調(diào)用方法對應(yīng)的MethodHandler的invoke方法

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

圖片圖片

雖然說默認(rèn)情況下SpringCloud使用是默認(rèn)實現(xiàn),最終使用FeignInvocationHandler

但是當(dāng)其它框架整合SpringCloud生態(tài)的時候,為了適配OpenFeign,有時會自己實現(xiàn)InvocationHandler

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

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

6、RequestInterceptor

圖片圖片

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

通過這個接口,在發(fā)送Http請求之前再對Http請求的內(nèi)容進行修改

比如我們可以設(shè)置一些接口需要的公共參數(shù),如鑒權(quán)token之類的

@Component
public class TokenRequestInterceptor implements RequestInterceptor {

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

}

7、Retryer

這是一個重試的組件,默認(rèn)實現(xiàn)如下

默認(rèn)情況下,最大重試5次

在SpringCloud下,并沒有使用上面那個實現(xiàn),而使用的是下面這個實現(xiàn)

圖片圖片

所以,SpringCloud下默認(rèn)是不會進行重試

小總結(jié)

這一節(jié)主要是介紹了7個Feign的核心組件以及Spring對應(yīng)的擴展實現(xiàn)

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

接口

作用

Feign默認(rèn)實現(xiàn)

Spring實現(xiàn)

Contract

解析方法注解和參數(shù),將Http請求參數(shù)和方法參數(shù)對應(yīng)

Contract.Default

SpringMvcContract

Encoder

將請求體對應(yīng)的方法參數(shù)序列化成字節(jié)數(shù)組

Encoder.Default

SpringEncoder

Decoder

將響應(yīng)體的字節(jié)流反序列化成方法返回值類型對象

Decoder.Default

SpringDecoder

Client

發(fā)送Http請求

Client.Default

LoadBalancerFeignClient

InvocationHandlerFactory

InvocationHandler工廠,動態(tài)代理核心邏輯

InvocationHandlerFactory.Default

RequestInterceptor

在發(fā)送Http請求之前,再對Http請求的內(nèi)容進行攔截修改

Retryer

重試組件

Retryer.Default

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

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

Feign核心運行原理分析

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

  • 動態(tài)代理生成原理
  • 一次Feign的Http調(diào)用執(zhí)行過程

1、動態(tài)代理生成原理

這里我先把上面的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)獲取到動態(tài)代理的

而上述代碼執(zhí)行邏輯如下所示:

圖片圖片

最終會調(diào)用ReflectiveFeign的newInstance方法來創(chuàng)建動態(tài)代理對象

而ReflectiveFeign內(nèi)部設(shè)置了前面提到的一些核心組件

接下我們來看看newInstance方法

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

第一件事首先解析接口,構(gòu)建每個方法對應(yīng)的MethodHandler

MethodHandler在前面講InvocationHandlerFactory特地提醒過

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

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

第二件事通過InvocationHandlerFactory創(chuàng)建InvocationHandler

然后再構(gòu)建出接口的動態(tài)代理對象

ok,到這其實就走完了動態(tài)代理的生成過程

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

圖片圖片

2、一次Feign的Http調(diào)用執(zhí)行過程

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

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

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

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

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

圖片圖片

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

這就是Feign一次Http調(diào)用的執(zhí)行過程

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

SpringCloud是如何整合Feign的?

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

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

第一部分核心組件重新實現(xiàn)前面已經(jīng)都說過了,這里就不再重復(fù)了

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

1、將FeignClient接口注冊到Spring中

使用OpenFeign時,必須加上@EnableFeignClients

這個注解就是OpenFeign的發(fā)動機

圖片圖片

@EnableFeignClients最后通過@Import注解導(dǎo)入了一個FeignClientsRegistrar

圖片圖片

FeignClientsRegistrar實現(xiàn)了ImportBeanDefinitionRegistrar

所以最終Spring在啟動的時候會調(diào)用registerBeanDefinitions方法實現(xiàn)

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

圖片圖片

最終會走到registerFeignClients這個方法

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

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

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

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

圖片圖片

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

圖片圖片

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

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

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

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

最終注冊到Spring容器中

圖片圖片

2、FeignClientFactoryBean的秘密

上一節(jié)說到,每個接口都對應(yīng)一個class類型為FeignClientFactoryBean的BeanDefinition

圖片圖片

如上所示,F(xiàn)eignClientFactoryBean是一個FactoryBean

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

圖片圖片

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

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

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

getObject最終會走到getTarget()方法

圖片圖片

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

只不過不同的是,SpringCloud會替換Feign默認(rèn)的組件,改成自己實現(xiàn)的

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

OpenFeign的各種配置方式以及對應(yīng)優(yōu)先級

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

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

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

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

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

圖片圖片

SpringCloud會給每個FeignClient容器添加一個默認(rèn)的配置類FeignClientsConfiguration配置類

圖片圖片

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

圖片圖片

所以,默認(rèn)情況下,OpenFeign就使用這些配置的組件構(gòu)建代理對象

知道配置隔離之后,接下來看看具體的幾種方式配置以及它們之間的優(yōu)先級關(guān)系

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

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

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

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

此時配置如下所示:

@EnableFeignClients(defaultConfiguration = FeignConfiguration.class)

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

并且優(yōu)先級大于默認(rèn)配置的優(yōu)先級

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

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

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

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

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

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

并且這種配置的優(yōu)先級是大于@EnableFeignClients注解配置的優(yōu)先級

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

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

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

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

這種配置的優(yōu)先級大于前面提到的所有配置優(yōu)先級

并且是對所有的FeignClient生效

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

4、配置文件

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

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

對所有FeignClient生效配置:

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

對單獨某個FeignClient生效配置:

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

在默認(rèn)情況下,這種配置文件方式優(yōu)先級最高

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

feign:
  client:
    default-to-properties: false

小總結(jié)

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

畫張圖來總結(jié)一下

圖片圖片

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


圖片圖片

圖片

責(zé)任編輯:武曉燕 來源: 三友的java日記
相關(guān)推薦

2023-10-16 22:07:20

Spring配置中心Bean

2024-01-02 22:47:47

Nacos注冊中心節(jié)點

2023-11-30 22:06:43

2024-07-08 23:03:13

2022-01-14 12:28:18

架構(gòu)OpenFeign遠程

2010-08-18 10:13:55

IntentAndroid

2009-06-15 15:57:21

Spring工作原理

2020-12-04 06:37:19

HTTPS原理安全

2009-08-25 13:48:01

Java EE架構(gòu)企業(yè)級應(yīng)用

2024-02-26 00:00:00

Nginx服務(wù)器HTTP

2019-12-12 10:56:00

微服務(wù)微服務(wù)架構(gòu)架構(gòu)

2025-01-10 09:47:43

blockSDKiOS

2023-12-05 17:44:24

reactor網(wǎng)絡(luò)

2010-03-12 17:09:18

2010-01-27 17:38:58

Windows Emb

2020-07-06 14:42:36

業(yè)務(wù)架構(gòu)IT架構(gòu)直播

2022-01-05 14:30:44

容器Linux網(wǎng)絡(luò)

2024-08-07 08:19:13

2024-01-05 07:55:39

Linux虛擬內(nèi)存

2025-03-11 10:58:00

點贊
收藏

51CTO技術(shù)棧公眾號