OpenFeign 核心原理

當(dāng)今是微服務(wù)橫行的時(shí)代,各個(gè)微服務(wù)之間相互調(diào)用是一件再平常不過(guò)的時(shí)候。在采用HTTP協(xié)議進(jìn)行通信的微服務(wù)中,我們自己可能去封裝一個(gè)HttpClient工具類(lèi)去進(jìn)行服務(wù)間的調(diào)用,封裝一個(gè)HttpClient工具,我們就需要考慮一下這些事情:

我們?cè)诎l(fā)送一個(gè)HTTP請(qǐng)求時(shí),我們需要選擇請(qǐng)求方式GET、POST、DELETE等,我們需要構(gòu)建請(qǐng)求參數(shù)、構(gòu)建請(qǐng)求頭信息等,那么作為一個(gè)工具類(lèi)我們是不是也要提供各種參數(shù)的靈活配置;

因?yàn)椴捎胷estful API 風(fēng)格的HTTP請(qǐng)求參數(shù)和返回?cái)?shù)據(jù)都是字符串的格式,那我們是否需要考慮序列化和反序列化問(wèn)題;

當(dāng)同一個(gè)服務(wù)部署到多臺(tái)服務(wù)器的時(shí)候,我們是不是應(yīng)該采用輪詢或者隨機(jī)的方式去選擇服務(wù)器,這也就是我們常說(shuō)的負(fù)載均衡。從另一方面來(lái)說(shuō)我們的核心是解決服務(wù)間的調(diào)用,但是我們?cè)谠O(shè)計(jì)一個(gè)通用HttpClient工具的時(shí)候是否也應(yīng)該支持負(fù)載均衡,以及如何和負(fù)載均衡高度解耦。

為此,大名鼎鼎的Feign應(yīng)時(shí)而生,我們?cè)趯W(xué)習(xí)Feign的實(shí)現(xiàn)的時(shí)候,我們應(yīng)該帶著這些問(wèn)題去學(xué)習(xí)Feign的實(shí)現(xiàn)原理。


一、什么是Feign

Feign 是聲明式 Web 服務(wù)客戶端,它使編寫(xiě) Web 服務(wù)客戶端更加容易 Feign 不做任何請(qǐng)求處理,通過(guò)處理注解相關(guān)信息生成 Request,并對(duì)調(diào)用返回的數(shù)據(jù)進(jìn)行解碼,從而實(shí)現(xiàn)簡(jiǎn)化HTTP API 的開(kāi)發(fā)。當(dāng)然你也可以直接使用 Apache HttpClient 來(lái)實(shí)現(xiàn)Web服務(wù)器客戶端,但是 Feign 的目的是盡量的減少資源和代碼來(lái)實(shí)現(xiàn)和 HTTP API 的連接。通過(guò)自定義的編碼解碼器以及錯(cuò)誤處理,你可以編寫(xiě)任何基于文本的 HTTP API。

如果要使用 Feign,需要?jiǎng)?chuàng)建一個(gè)接口并對(duì)其添加 Feign 相關(guān)注解,另外 Feign 還支持可插拔編碼器和解碼器,致力于打造一個(gè)輕量級(jí) HTTP 客戶端。

如果你想直接使用原生的Feign的話,你可以去參考Feign配置使用,下面就是Feign針對(duì)一個(gè)HTTP API的接口定義:

interface GitHub {

? // RequestLine注解聲明請(qǐng)求方法和請(qǐng)求地址,可以允許有查詢參數(shù)

? @RequestLine("GET /user/list")

? List<User> list();

}

目前由于Spring Cloud微服務(wù)的廣泛使用,廣大開(kāi)發(fā)者更傾向于使用spring-cloud-starter-openfeign,Spring Cloud 添加了對(duì) Spring MVC 注解的支持,在微服務(wù)中我們的接口定義有所變化:

@FeignClient(name="服務(wù)名",contextId="唯一標(biāo)識(shí)")

interface GitHub {

? @GetMapping("/user/list")

? List<User> list();

}


二、Feign 和 Openfeign 的區(qū)別

Feign 最早是由 Netflix 公司進(jìn)行維護(hù)的,后來(lái) Netflix 不再對(duì)其進(jìn)行維護(hù),最終 Feign 由社區(qū)進(jìn)行維護(hù),更名為 Openfeign。


2.1 Starter OpenFeign

當(dāng)然了,基于 SpringCloud 團(tuán)隊(duì)對(duì) Netflix 的情有獨(dú)鐘,你出了這么好用的輕量級(jí) HTTP 客戶端,我這老大哥不得支持一下,所以就有了基于 Feign 封裝的 Starter。

<dependency>

? ? <groupId>org.springframework.cloud</groupId>

? ? <artifactId>spring-cloud-starter-openfeign</artifactId>

</dependency>

這里面有兩個(gè)非常重要的包:

一個(gè)是spring-cloud-openfeign-core,這個(gè)包是SpringCloud支持Feign的核心包,Spring Cloud 添加了對(duì) Spring MVC 注解的支持(通過(guò)SpringMvcContract實(shí)現(xiàn)),并支持使用 Spring Web 中默認(rèn)使用的相同 HttpMessageConverters。另外,Spring Cloud同時(shí)集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用 Feign 時(shí)提供負(fù)載均衡的 HTTP客戶端。針對(duì)于注冊(cè)中心的支持,包含但不限于 Eureka,比如 Consul、Naocs 等注冊(cè)中心均支持。

另一個(gè)包是feign-core,也就是feign的原生包,具體使用細(xì)節(jié)可以參考Feign配置使用。通俗點(diǎn)說(shuō),spring-cloud-openfeign-core就是通過(guò)一系列的配置創(chuàng)建Feign.builder()實(shí)例的過(guò)程。

在我們 SpringCloud 項(xiàng)目開(kāi)發(fā)過(guò)程中,使用的大多都是這個(gè) Starter Feign。


2.2 demo

為了方便大家理解,這里寫(xiě)出對(duì)應(yīng)的生產(chǎn)方、消費(fèi)方 Demo 代碼,以及使用的注冊(cè)中心。

生產(chǎn)者服務(wù):添加 Nacos 服務(wù)注冊(cè)發(fā)現(xiàn)注解以及發(fā)布出 HTTP 接口服務(wù)

@EnableDiscoveryClient

@SpringBootApplication

public class NacosProduceApplication {

? ? public static void main(String[] args) {

? ? ? ? SpringApplication.run(NacosProduceApplication.class, args);

? ? }

? ? @RestController

? ? static class TestController {

? ? ? ? @GetMapping("/hello")

? ? ? ? public String hello(@RequestParam("name") String name) {

? ? ? ? ? ? return "hello " + name;

? ? ? ? }

? ? }

}


消費(fèi)者服務(wù):

定義 FeignClient 消費(fèi)服務(wù)接口

@FeignClient(name= "nacos-produce",contextId="DemoFeignClient")

public interface DemoFeignClient {

? ? @GetMapping("/hello")

? ? String sayHello(@RequestParam("name") String name);

}

因?yàn)樯a(chǎn)者使用 Nacos,所以消費(fèi)者除了開(kāi)啟 Feign 注解,同時(shí)也要開(kāi)啟 Naocs 服務(wù)注冊(cè)發(fā)現(xiàn)。

@RestController

@EnableFeignClients

@EnableDiscoveryClient

@SpringBootApplication

public class NacosConsumeApplication {

? ? public static void main(String[] args) {

? ? ? ? SpringApplication.run(NacosConsumeApplication.class, args);

? ? }

? ? @Autowired private DemoFeignClient demoFeignClient;

? ? @GetMapping("/test")

? ? public String test() {

? ? ? ? String result = demoFeignClient.sayHello("xxxxx");

? ? ? ? return result;

? ? }

}


三、Feign 的啟動(dòng)原理

下文中調(diào)試中使用的代碼并不是demo中的代碼,不過(guò)和demo使用的類(lèi)似,只是業(yè)務(wù)系統(tǒng)更加復(fù)雜而已。

我們?cè)?SpringCloud 的使用過(guò)程中,如果想要啟動(dòng)某個(gè)組件,一般都是 @Enable... 這種方式注入,F(xiàn)eign 也不例外,我們需要在類(lèi)上標(biāo)記此注解 @EnableFeignClients。


3.1 @EnableFeignClients

EnableFeignClients注解,用于掃描使用@FeignClient注解標(biāo)注的接口, 而該功能是通過(guò)@Import(FeignClientsRegistrar.class)完成。


@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Import(FeignClientsRegistrar.class)

public @interface EnableFeignClients {

? ? String[] value() default {};

? ? String[] basePackages() default {};

? ? Class<?>[] basePackageClasses() default {};

? ? Class<?>[] defaultConfiguration() default {};

? ? Class<?>[] clients() default {};

}

FeignClientsRegistrar實(shí)現(xiàn)了ImportBeanDefinitionRegistrar, 用以完成相關(guān)Bean注冊(cè)。


ImportBeanDefinitionRegistrar 負(fù)責(zé)動(dòng)態(tài)注入 IOC Bean,分別注入 Feign 配置類(lèi)、FeignClient。

class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{

? ? ? ? @Override

? ? public void registerBeanDefinitions(AnnotationMetadata metadata,

? ? ? ? ? ? BeanDefinitionRegistry registry) {

? ? ? ? registerDefaultConfiguration(metadata, registry);

? ? ? ? registerFeignClients(metadata, registry);

? ? }

? ? ? ...

}


3.2 registerDefaultConfiguration(metadata, registry)

private void registerDefaultConfiguration(AnnotationMetadata metadata,

? ? ? ? ? ? BeanDefinitionRegistry registry) {

? ? ? ? Map<String, Object> defaultAttrs = metadata

? ? ? ? ? ? ? ? .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

? ? ? ? if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {

? ? ? ? ? ? String name;

? ? ? ? ? ? if (metadata.hasEnclosingClass()) {

? ? ? ? ? ? ? ? name = "default." + metadata.getEnclosingClassName();

? ? ? ? ? ? }

? ? ? ? ? ? else {

? ? ? ? ? ? ? ? name = "default." + metadata.getClassName();

? ? ? ? ? ? }

? ? ? ? ? ? registerClientConfiguration(registry, name,

? ? ? ? ? ? ? ? ? ? defaultAttrs.get("defaultConfiguration"));

? ? ? ? }

? ? }

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,

? ? ? ? ? ? Object configuration) {

? ? ? ? BeanDefinitionBuilder builder = BeanDefinitionBuilder

? ? ? ? ? ? ? ? .genericBeanDefinition(FeignClientSpecification.class);

? ? ? ? builder.addConstructorArgValue(name);

? ? ? ? builder.addConstructorArgValue(configuration);

? ? ? ? registry.registerBeanDefinition(

? ? ? ? ? ? ? ? name + "." + FeignClientSpecification.class.getSimpleName(),

? ? ? ? ? ? ? ? builder.getBeanDefinition());

? ? }

1. 獲取 @EnableFeignClients 注解上的屬性以及對(duì)應(yīng) value。

2.使用BeanDefinitionBuilder構(gòu)造器為FeignClientSpecification類(lèi)生成BeanDefinition,這個(gè)BeanDefinition是對(duì)FeignClientSpecification Bean的定義,保存了FeignClientSpecification? Bean 的各種信息,如屬性、構(gòu)造方法參數(shù)等。其中@EnableFeignClients 注解上的defaultConfiguration屬性就是作為構(gòu)造方法參數(shù)傳入的。而bean名稱為 default. + @EnableFeignClients 修飾類(lèi)(一般是啟動(dòng)類(lèi))全限定名稱 + FeignClientSpecification

3.@EnableFeignClients defaultConfiguration 默認(rèn)為 {},如果沒(méi)有相關(guān)配置,默認(rèn)使用 FeignClientsConfiguration 并結(jié)合 name 填充到 FeignClientSpecification,最終會(huì)被注冊(cè)為 IOC Bean

總結(jié)下來(lái),就是根據(jù)@EnableFeignClients中屬性defaultConfiguration,為FeignClientSpecification類(lèi)型生成BeanDefinition,并注入Spriing容器中。


3.3 registerFeignClients(metadata, registry)

public void registerFeignClients(AnnotationMetadata metadata,

? ? ? ? ? ? BeanDefinitionRegistry registry) {

? ? ? ? ClassPathScanningCandidateComponentProvider scanner = getScanner();

? ? ? ? scanner.setResourceLoader(this.resourceLoader);

? ? ? ? Set<String> basePackages;

? ? ? ? Map<String, Object> attrs = metadata

? ? ? ? ? ? ? ? .getAnnotationAttributes(EnableFeignClients.class.getName());

? ? ? ? AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(

? ? ? ? ? ? ? ? FeignClient.class);

? ? ? ? final Class<?>[] clients = attrs == null ? null

? ? ? ? ? ? ? ? : (Class<?>[]) attrs.get("clients");

? ? ? ? if (clients == null || clients.length == 0) {

? ? ? ? ? ? scanner.addIncludeFilter(annotationTypeFilter);

? ? ? ? ? ? basePackages = getBasePackages(metadata);

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? final Set<String> clientClasses = new HashSet<>();

? ? ? ? ? ? basePackages = new HashSet<>();

? ? ? ? ? ? for (Class<?> clazz : clients) {

? ? ? ? ? ? ? ? basePackages.add(ClassUtils.getPackageName(clazz));

? ? ? ? ? ? ? ? clientClasses.add(clazz.getCanonicalName());

? ? ? ? ? ? }

? ? ? ? ? ? AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {

? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? protected boolean match(ClassMetadata metadata) {

? ? ? ? ? ? ? ? ? ? String cleaned = metadata.getClassName().replaceAll("\\$", ".");

? ? ? ? ? ? ? ? ? ? return clientClasses.contains(cleaned);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? };

? ? ? ? ? ? scanner.addIncludeFilter(

? ? ? ? ? ? ? ? ? ? new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));

? ? ? ? }

? ? ? ? for (String basePackage : basePackages) {

? ? ? ? ? ? Set<BeanDefinition> candidateComponents = scanner

? ? ? ? ? ? ? ? ? ? .findCandidateComponents(basePackage);

? ? ? ? ? ? for (BeanDefinition candidateComponent : candidateComponents) {

? ? ? ? ? ? ? ? if (candidateComponent instanceof AnnotatedBeanDefinition) {

? ? ? ? ? ? ? ? ? ? // verify annotated class is an interface

? ? ? ? ? ? ? ? ? ? AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;

? ? ? ? ? ? ? ? ? ? AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();

? ? ? ? ? ? ? ? ? ? Assert.isTrue(annotationMetadata.isInterface(),

? ? ? ? ? ? ? ? ? ? ? ? ? ? "@FeignClient can only be specified on an interface");

? ? ? ? ? ? ? ? ? ? Map<String, Object> attributes = annotationMetadata

? ? ? ? ? ? ? ? ? ? ? ? ? ? .getAnnotationAttributes(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? FeignClient.class.getCanonicalName());

? ? ? ? ? ? ? ? ? ? String name = getClientName(attributes);

? ? ? ? ? ? ? ? ? ? registerClientConfiguration(registry, name,

? ? ? ? ? ? ? ? ? ? ? ? ? ? attributes.get("configuration"));

? ? ? ? ? ? ? ? ? ? registerFeignClient(registry, annotationMetadata, attributes);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

1. 掃描使用FeignClient注解標(biāo)注的接口,獲取basePackages掃描路徑 。

2.根據(jù)獲取到的掃描路徑,然后根據(jù)掃描路徑,獲取該路徑將其子路徑下,使用FeignClient注解標(biāo)記的接口。

3.遍歷每一個(gè)FeignClient注解的類(lèi):

收集接口FeignClient注解屬性信息,并根據(jù) configuration 屬性去創(chuàng)建接口級(jí)的 FeignClientSpecification BeanDefinition,然后注入Spring容器。


生成FeignClientFactoryBean 類(lèi)型的BeanDefinition,并將 @FeignClient 的屬性設(shè)置到 FeignClientFactoryBean 對(duì)象上,然后注入Spring容器。

其中需要注意,在將@FeignClient 的屬性設(shè)置到 FeignClientFactoryBean 對(duì)象,會(huì)將@FeignClient的修飾的類(lèi)的className作為type屬性,傳遞給FeignClientFactoryBean,后續(xù)正是通過(guò)這個(gè),創(chuàng)建對(duì)應(yīng)的代理類(lèi)。

總結(jié)下來(lái),就是為一個(gè)@FeignClient創(chuàng)建一個(gè)FeignClientSpecification、FeignClientFactoryBean,F(xiàn)eignClientSpecification保存這個(gè)@FeignClient的configuration 屬性信息,而FeignClientFactoryBean中收集了這個(gè)FeignClient其他的屬性。

由于FeignClientFactoryBean 繼承自 FactoryBean,也就是說(shuō),當(dāng)我們定義 @FeignClient 修飾接口時(shí),注冊(cè)到 IOC 容器中 Bean 類(lèi)型變成了 FeignClientFactoryBean,在 Spring 中,F(xiàn)actoryBean 是一個(gè)工廠 Bean,用來(lái)創(chuàng)建代理 Bean。工廠 Bean 是一種特殊的 Bean,對(duì)于需要獲取 Bean 的消費(fèi)者而言,它是不知道 Bean 是普通 Bean 或是工廠 Bean 的。工廠 Bean 返回的實(shí)例不是工廠 Bean 本身,該實(shí)例是由工廠 Bean 中 FactoryBean#getObject 邏輯所創(chuàng)建的。更多FactoryBean相關(guān)的信息,可以閱讀我之前的博客。


四、 FeignClient創(chuàng)建過(guò)程分析

上面說(shuō)到 @FeignClient 修飾的接口最終填充到 IOC 容器的類(lèi)型是 FeignClientFactoryBean,先來(lái)看下它是什么。


4.1 FactoryBean 接口特征

1 .它會(huì)在類(lèi)初始化時(shí)執(zhí)行一段邏輯,依據(jù)InitializingBean 接口。

2.如果它被別的類(lèi) @Autowired 進(jìn)行注入,返回的不是它本身,而是 FactoryBean#getObject 返回的類(lèi),依據(jù) Spring FactoryBean 接口。

3.它能夠獲取 Spring 上下文對(duì)象,依據(jù) Spring ApplicationContextAware 接口。

先來(lái)看它的初始化邏輯都執(zhí)行了什么:

@Override

public void afterPropertiesSet() {

? ? Assert.hasText(contextId, "Context id must be set");

? ? Assert.hasText(name, "Name must be set");

}

沒(méi)有特別的操作,只是使用斷言工具類(lèi)判斷兩個(gè)字段不為空。ApplicationContextAware 也沒(méi)什么說(shuō)的,獲取上下文對(duì)象賦值到對(duì)象的局部變量里,重點(diǎn)以及關(guān)鍵就是 FactoryBean#getObject 方法。

@Override

public Object getObject() throws Exception {

? ? return getTarget();

}


4.2 FeignClientFactoryBean#getTarget

getTarget 源碼方法還是挺長(zhǎng)的,這里采用分段的形式展示:

<T> T getTarget() {

? // 從 IOC 容器獲取 FeignContext

? ? FeignContext context = applicationContext.getBean(FeignContext.class);

? // 通過(guò) context 創(chuàng)建 Feign 構(gòu)造器

? ? Feign.Builder builder = feign(context);

? ...

}


這里提出一個(gè)疑問(wèn)?FeignContext 什么時(shí)候、在哪里被注入到 Spring 容器里的?

用了 SpringBoot 怎么會(huì)不使用自動(dòng)裝配的功能呢,F(xiàn)eignContext 就是在 FeignAutoConfiguration 中被成功創(chuàng)建。


在FeignAutoConfiguration中,向Spring容器注入FeignContext :

并設(shè)置其配置為configurations ,而configurations 是通過(guò)@Autowired注入,即List集合。

@Configuration(proxyBeanMethods = false)

@ConditionalOnClass(Feign.class)

@EnableConfigurationProperties({ FeignClientProperties.class,

? ? ? ? FeignHttpClientProperties.class })

@Import(DefaultGzipDecoderConfiguration.class)

public class FeignAutoConfiguration {

? ? @Autowired(required = false)

? ? private List<FeignClientSpecification> configurations = new ArrayList<>();

? ? @Bean

? ? public HasFeatures feignFeature() {

? ? ? ? return HasFeatures.namedFeature("Feign", Feign.class);

? ? }

? ? @Bean

? ? public FeignContext feignContext() {

? ? ? ? FeignContext context = new FeignContext();

? ? ? ? context.setConfigurations(this.configurations);

? ? ? ? return context;

? ? }

? ? ? ...

}

4.3 FeignClientFactoryBean#feign

protected Feign.Builder feign(FeignContext context) {

? ? ? ? FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

? ? ? ? Logger logger = loggerFactory.create(this.type);

? ? ? ? // @formatter:off

? ? ? ? Feign.Builder builder = get(context, Feign.Builder.class)

? ? ? ? ? ? ? ? // required values

? ? ? ? ? ? ? ? .logger(logger)

? ? ? ? ? ? ? ? .encoder(get(context, Encoder.class))

? ? ? ? ? ? ? ? .decoder(get(context, Decoder.class))

? ? ? ? ? ? ? ? .contract(get(context, Contract.class));

? ? ? ? // @formatter:on

? ? ? ? configureFeign(context, builder);

? ? ? ? return builder;

? ? }

feign 方法里日志工廠、編碼、解碼等類(lèi)均是通過(guò)FeignClientFactoryBean#get(...) 方法得到。

protected <T> T get(FeignContext context, Class<T> type) {

? ? ? ? T instance = context.getInstance(this.contextId, type);

? ? ? ? if (instance == null) {

? ? ? ? ? ? throw new IllegalStateException(

? ? ? ? ? ? ? ? ? ? "No bean found of type " + type + " for " + this.contextId);

? ? ? ? }

? ? ? ? return instance;

? ? }

? ? //FeignContext方法

? ? public <T> T getInstance(String name, Class<T> type) {

? ? ? ? //根據(jù)name獲取context實(shí)例

? ? ? ? AnnotationConfigApplicationContext context = getContext(name);

? ? ? ? //根據(jù)type類(lèi)型從子容器獲取Bean實(shí)例? ?

? ? ? ? if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,

? ? ? ? ? ? ? ? type).length > 0) {

? ? ? ? ? ? return context.getBean(type);

? ? ? ? }

? ? ? ? return null;

? ? }

這里涉及到 Spring 父子容器的概念,默認(rèn)子容器FeignContext#contexts為空,獲取不到服務(wù)名對(duì)應(yīng) context 則使用FeignContext#createContext新建。

private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();


protected AnnotationConfigApplicationContext getContext(String name) {

? ? ? ? if (!this.contexts.containsKey(name)) {

? ? ? ? ? ? synchronized (this.contexts) {

? ? ? ? ? ? ? ? if (!this.contexts.containsKey(name)) {

? ? ? ? ? ? ? ? ? ? this.contexts.put(name, createContext(name));

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return this.contexts.get(name);

? ? }


子容器context創(chuàng)建完之后,如果@FeignClient中配置有configuration。會(huì)向子容器中注入一個(gè) configuration屬性指定的類(lèi)型的 Bean。因此我們可以通過(guò)configuration對(duì)每個(gè)@FeignClient做定制化配置、比如Encoder、Decoder、FeignLoggerFactory等等。

//這里的name是@FeignContent中的contentId值

? protected AnnotationConfigApplicationContext createContext(String name) {

? ? ? ? AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

? ? ? ? //@FeignClient沒(méi)有配置configuration屬性 不會(huì)執(zhí)行? this.configurations 保存的是FeignClientConfiguration類(lèi)型的列表,也就是之前我們介紹到的注入Spring容器中的FeignClient配置

? ? ? ? if (this.configurations.containsKey(name)) {

? ? ? ? ? ? for (Class<?> configuration : this.configurations.get(name)

? ? ? ? ? ? ? ? ? ? .getConfiguration()) {

? ? ? ? ? ? ? ? context.register(configuration);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? for (Map.Entry<String, C> entry : this.configurations.entrySet()) {

? ? ? ? ? ? if (entry.getKey().startsWith("default.")) {

? ? ? ? ? ? ? ? // @EnableFeignClient沒(méi)有配置defaultConfiguration屬性 不會(huì)執(zhí)行

? ? ? ? ? ? ? ? for (Class<?> configuration : entry.getValue().getConfiguration()) {

? ? ? ? ? ? ? ? ? ? context.register(configuration);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? // 注入默認(rèn)配置類(lèi)FeignClientsConfiguration,會(huì)注入默認(rèn)的feignEncoder、feignDecoder等

? ? ? ? context.register(PropertyPlaceholderAutoConfiguration.class,

? ? ? ? ? ? ? ? this.defaultConfigType);

? ? ? ? context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(

? ? ? ? ? ? ? ? this.propertySourceName,

? ? ? ? ? ? ? ? Collections.<String, Object>singletonMap(this.propertyName, name)));

? ? ? ? if (this.parent != null) {

? ? ? ? ? ? // Uses Environment from parent as well as beans

? ? ? ? ? ? //設(shè)置父容器、子容器不存在去父容器查找

? ? ? ? ? ? context.setParent(this.parent);

? ? ? ? ? ? // jdk11 issue

? ? ? ? ? ? // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101

? ? ? ? ? ? context.setClassLoader(this.parent.getClassLoader());

? ? ? ? }

? ? ? ? context.setDisplayName(generateDisplayName(name));

? ? ? ? context.refresh();

? ? ? ? return context;

? ? }

關(guān)于父子類(lèi)容器對(duì)應(yīng)關(guān)系,以及提供 @FeignClient 服務(wù)對(duì)應(yīng)子容器的關(guān)系(每一個(gè)服務(wù)對(duì)應(yīng)一個(gè)子容器實(shí)例)

需要注意的是上圖中的Product1、Product2、Product3并不是說(shuō)就有三個(gè)微服務(wù)。而是說(shuō)有三個(gè)@FeignClien服務(wù),三個(gè)服務(wù)可以對(duì)應(yīng)一個(gè)微服務(wù),比如下面這種:

Client 1

@FeignClient(name = "optimization-user",contextId="1")

public interface UserRemoteClient {

? ? @GetMapping("/user/get")

? ? public User getUser(@RequestParam("id") int id);

}

Client 2

@FeignClient(name = "optimization-user",,contextId="2")

public interface UserRemoteClient2 {

? ? @GetMapping("/user2/get")

? ? public User getUser(@RequestParam("id") int id);

}

Client 3

@FeignClient(name = "optimization-user",,contextId="3")

public interface UserRemoteClient2 {

? ? @GetMapping("/user2/get")

? ? public User getUser(@RequestParam("id") int id);

}

回到FeignContext#getInstance 方法,子容器此時(shí)已加載對(duì)應(yīng) Bean,直接通過(guò) getBean 獲取 FeignLoggerFactory。

如法炮制,F(xiàn)eign.Builder、Encoder、Decoder、Contract 都可以通過(guò)子容器獲取對(duì)應(yīng) Bean。

protected Feign.Builder feign(FeignContext context) {

? ? ? ? FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

? ? ? ? Logger logger = loggerFactory.create(this.type);

? ? ? ? // @formatter:off

? ? ? ? Feign.Builder builder = get(context, Feign.Builder.class)

? ? ? ? ? ? ? ? // required values

? ? ? ? ? ? ? ? .logger(logger)

? ? ? ? ? ? ? ? .encoder(get(context, Encoder.class))

? ? ? ? ? ? ? ? .decoder(get(context, Decoder.class))

? ? ? ? ? ? ? ? .contract(get(context, Contract.class));

? ? ? ? // @formatter:on

? ? ? ? configureFeign(context, builder);

? ? ? ? return builder;

? ? }


configureFeign 方法主要進(jìn)行一些配置賦值,比如超時(shí)、重試、404 配置等,就不再細(xì)說(shuō)了。

到這里有必要總結(jié)一下創(chuàng)建 Spring 代理工廠的前半場(chǎng)代碼 :

1. 注入@FeignClient 服務(wù)時(shí),其實(shí)注入的是 FactoryBean#getObject 返回代理工廠對(duì)象。

2.通過(guò) IOC 容器獲取 FeignContext 上下文。

3.,創(chuàng)建 Feign.Builder 對(duì)象時(shí)會(huì)創(chuàng)建 Feign 服務(wù)對(duì)應(yīng)的子容器。

4.從子容器中獲取日志工廠、編碼器、解碼器等 Bean 為 Feign.Builder 設(shè)置配置,比如超時(shí)時(shí)間、日志級(jí)別等屬性,每一個(gè)服務(wù)都可以個(gè)性化設(shè)置。


4.4 動(dòng)態(tài)生成代理

接下來(lái)是最最最重要的地方了,繼續(xù)FeignClientFactoryBean#getTarget

<T> T getTarget() {

? ? ? ? FeignContext context = this.applicationContext.getBean(FeignContext.class);

? ? ? ? Feign.Builder builder = feign(context);

? ? ? ? //判斷@FeignClient url屬性是否存在

? ? ? ? if (!StringUtils.hasText(this.url)) {

? ? ? ? ? ? if (!this.name.startsWith("http")) {

? ? ? ? ? ? ? ? this.url = "http://" + this.name;

? ? ? ? ? ? }

? ? ? ? ? ? else {

? ? ? ? ? ? ? ? this.url = this.name;

? ? ? ? ? ? }

? ? ? ? ? ? this.url += cleanPath();

? ? ? ? ? //type就是@FeignClient注解修飾的接口類(lèi)型

? ? ? ? ? //name:@FeignClient name屬性,ribbon通過(guò)這個(gè)到注冊(cè)中心獲取服務(wù)信息

? ? ? ? ? ? return (T) loadBalance(builder, context,

? ? ? ? ? ? ? ? ? ? new HardCodedTarget<>(this.type, this.name, this.url));

? ? ? ? }

? ? ? ? //存在的話,就不使用負(fù)載均衡以及注冊(cè)中心了

? ? ? ? ? ? ? ...

}

因?yàn)槲覀冊(cè)?@FeignClient 注解是使用 name 而不是 url,所以會(huì)執(zhí)行負(fù)載均衡策略的分支。FeignClientFactoryBean#loadBalance:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,

? ? ? ? ? ? HardCodedTarget<T> target) {

? ? ? ? //從

? ? ? ? Client client = getOptional(context, Client.class);

? ? ? ? if (client != null) {

? ? ? ? ? ? builder.client(client);

? ? ? ? ? ? Targeter targeter = get(context, Targeter.class);

? ? ? ? ? ? return targeter.target(this, builder, context, target);

? ? ? ? }

? ? ? ? throw new IllegalStateException(

? ? ? ? ? ? ? ? "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");

? ? }


Client:Feign 發(fā)送請(qǐng)求以及接收響應(yīng)等都是由 Client 完成,該類(lèi)默認(rèn) Client.Default,另外支持 HttpClient、OkHttp 等客戶端,如:

Feign.builder().client(new OkHttpClient())

代碼中的 Client、Targeter 在自動(dòng)裝配時(shí)注冊(cè),配合上文中的父子容器理論,這兩個(gè) Bean 在父容器中存在,所以子容器也可以獲取到。FeignClientFactoryBean#getOptional,getOptional和get的區(qū)別在于一個(gè)是可選,一個(gè)是必須的,get中如果從子容器獲取不到指定的bean實(shí)例,會(huì)拋出異常,而getOptional不會(huì):

protected <T> T getOptional(FeignContext context, Class<T> type) {

? ? ? ? return context.getInstance(this.contextId, type);

? ? }

因?yàn)槲覀儾](méi)有對(duì) Hystix 進(jìn)行設(shè)置,所以Targeter#target走入Feign#target分支:

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,

? ? ? ? ? ? FeignContext context, Target.HardCodedTarget<T> target) {

? ? ? ? if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {

? ? ? ? ? ? return feign.target(target);

? ? ? ? }

? ? ? ? feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;

? ? ? ? String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()

? ? ? ? ? ? ? ? : factory.getContextId();

? ? ? ? SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);

? ? ? ? if (setterFactory != null) {

? ? ? ? ? ? builder.setterFactory(setterFactory);

? ? ? ? }

? ? ? ? Class<?> fallback = factory.getFallback();

? ? ? ? if (fallback != void.class) {

? ? ? ? ? ? return targetWithFallback(name, context, target, builder, fallback);

? ? ? ? }

? ? ? ? Class<?> fallbackFactory = factory.getFallbackFactory();

? ? ? ? if (fallbackFactory != void.class) {

? ? ? ? ? ? return targetWithFallbackFactory(name, context, target, builder,

? ? ? ? ? ? ? ? ? ? fallbackFactory);

? ? ? ? }

? ? ? ? return feign.target(target);

? ? }

Feign#target中首先會(huì)創(chuàng)建反射類(lèi) ReflectiveFeign,其中ReflectiveFeign是Feign的實(shí)現(xiàn)類(lèi):

然后調(diào)用ReflectiveFeign#newInstance(target)執(zhí)行創(chuàng)建實(shí)例類(lèi):

public <T> T target(Target<T> target) {

? ? ? return build().newInstance(target);

? ? }

public Feign build() {

? ? ? SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =

? ? ? ? ? new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,

? ? ? ? ? ? ? logLevel, decode404, closeAfterDecode, propagationPolicy);

? ? ? ParseHandlersByName handlersByName =

? ? ? ? ? new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,

? ? ? ? ? ? ? errorDecoder, synchronousMethodHandlerFactory);

? ? ? return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);

? ? }

ReflectiveFeign#newInstance 方法對(duì) @FeignClient 修飾的接口中 SpringMvc 等配置進(jìn)行解析轉(zhuǎn)換,對(duì)接口類(lèi)中的方法進(jìn)行歸類(lèi),生成動(dòng)態(tài)代理類(lèi)

public <T> T newInstance(Target<T> target) {

? ? //將裝飾了@FeignClient的接口方法封裝為方法處理器,包括Spring MVC注解邏輯處理

? ? Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

? ? //接口方法對(duì)應(yīng)的MethodHandler

? ? Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();

? //添加JDK8以后出現(xiàn)的接口中默認(rèn)方法

? ? List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

? ? //1.如果是object 方法跳過(guò)? 2.default方法添加defaultMethodHandlers 3、否則添加methodToHandler

? ? for (Method method : target.type().getMethods()) {

? ? ? if (method.getDeclaringClass() == Object.class) {

? ? ? ? continue;

? ? ? } else if (Util.isDefault(method)) {

? ? ? ? DefaultMethodHandler handler = new DefaultMethodHandler(method);

? ? ? ? defaultMethodHandlers.add(handler);

? ? ? ? methodToHandler.put(method, handler);

? ? ? } else {

? ? ? ? methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));

? ? ? }

? ? }

? ? //根據(jù)targert、methodToHandler創(chuàng)建InvocationHandler

? ? InvocationHandler handler = factory.create(target, methodToHandler);

? ? //根據(jù)JDK Proxy創(chuàng)建動(dòng)態(tài)代理類(lèi)

? ? T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),

? ? ? ? new Class<?>[] {target.type()}, handler);

? ? for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {

? ? ? defaultMethodHandler.bindTo(proxy);

? ? }

? ? return proxy;

? }

可以看出 Feign 創(chuàng)建動(dòng)態(tài)代理類(lèi)的方式和 Mybatis Mapper 處理方式是一致的,因?yàn)閮烧叨紱](méi)有實(shí)現(xiàn)類(lèi) 。

根據(jù) ReflectiveFeign#newInstance 方法按照行為大致劃分,共做了四件事 處理:

\1. 將@FeignClient 接口方法封裝為 MethodHandler 包裝類(lèi),每一個(gè)方法對(duì)應(yīng)一個(gè)MethodHandler,MethodHandler的實(shí)現(xiàn)類(lèi)是SynchronousMethodHandler。

? ? public MethodHandler create(Target<?> target,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MethodMetadata md,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? RequestTemplate.Factory buildTemplateFromArgs,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Options options,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Decoder decoder,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ErrorDecoder errorDecoder) {

? ? ? return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,

? ? ? ? ? logLevel, md, buildTemplateFromArgs, options, decoder,

? ? ? ? ? errorDecoder, decode404, closeAfterDecode, propagationPolicy, forceDecoding);

? ? }


1、可以看到每個(gè)MethodHandler都包含了客戶端、日志、請(qǐng)求模板、編碼解碼器等參數(shù),通過(guò)這些參數(shù)就可以構(gòu)建相應(yīng)接口的Http請(qǐng)求。

2. 遍歷接口中所有方法,過(guò)濾 Object 方法,并將默認(rèn)方法以及 FeignClient 方法分類(lèi)。

3. 創(chuàng)建動(dòng)態(tài)代理對(duì)應(yīng)的 InvocationHandler ,默認(rèn)InvocationHandler 的實(shí)現(xiàn)類(lèi)為ReflectiveFeign.FeignInvocationHandler,然后利用Proxy.newProxyInstance創(chuàng)建 Proxy 實(shí)例。

4. 接口內(nèi) default 方法綁定動(dòng)態(tài)代理類(lèi)。

其中FeignInvocationHandler實(shí)現(xiàn)如下:

static class FeignInvocationHandler implements InvocationHandler {

? ? private final Target target;

? ? private final Map<Method, MethodHandler> dispatch;

? ? FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {

? ? ? this.target = checkNotNull(target, "target");

? ? ? this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);

? ? }

? ? @Override

? ? public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

? ? ? if ("equals".equals(method.getName())) {

? ? ? ? try {

? ? ? ? ? Object otherHandler =

? ? ? ? ? ? ? args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;

? ? ? ? ? return equals(otherHandler);

? ? ? ? } catch (IllegalArgumentException e) {

? ? ? ? ? return false;

? ? ? ? }

? ? ? } else if ("hashCode".equals(method.getName())) {

? ? ? ? return hashCode();

? ? ? } else if ("toString".equals(method.getName())) {

? ? ? ? return toString();

? ? ? }

? ? ? return dispatch.get(method).invoke(args);

? ? }

dispath 是緩存的method 以及 method對(duì)應(yīng)的MethodHandler

我們調(diào)用的遠(yuǎn)程接口用的是SynchronousMethodHandler實(shí)現(xiàn),該類(lèi)將方法參數(shù)、方法返回值、參數(shù)集合、請(qǐng)求類(lèi)型、請(qǐng)求路徑進(jìn)行解析存儲(chǔ)。

到這里我們已經(jīng)很清楚Feign 的工作方式了。前面那么多封裝鋪墊,封裝個(gè)性化配置等等,最終確定收尾的是創(chuàng)建動(dòng)態(tài)代理類(lèi)。

也就是說(shuō)在我們調(diào)用 feign接口時(shí),會(huì)被 ReflectiveFeign.FeignInvocationHandler#invoke 攔截,最后會(huì)被SynchronousMethodHandler#invoke方法處理。


4.5 調(diào)試運(yùn)行

既然已經(jīng)明白了調(diào)用流程,那就正兒八經(jīng)的試一哈,就會(huì)發(fā)現(xiàn)我們通過(guò)FeignClient服務(wù)發(fā)送的請(qǐng)求,最終會(huì)被SynchronousMethodHandler#invoke方法處理,該方法首先會(huì)根據(jù)請(qǐng)求參數(shù)構(gòu)建請(qǐng)求模板:

public Object invoke(Object[] argv) throws Throwable {

? ? RequestTemplate template = buildTemplateFromArgs.create(argv);

? ? Options options = findOptions(argv);

? ? Retryer retryer = this.retryer.clone();

? ? while (true) {

? ? ? try {

? ? ? ? return executeAndDecode(template, options);

? ? ? } catch (RetryableException e) {

? ? ? ? try {

? ? ? ? ? retryer.continueOrPropagate(e);

? ? ? ? } catch (RetryableException th) {

? ? ? ? ? Throwable cause = th.getCause();

? ? ? ? ? if (propagationPolicy == UNWRAP && cause != null) {

? ? ? ? ? ? throw cause;

? ? ? ? ? } else {

? ? ? ? ? ? throw th;

? ? ? ? ? }

? ? ? ? }

? ? ? ? if (logLevel != Logger.Level.NONE) {

? ? ? ? ? logger.logRetry(metadata.configKey(), logLevel);

? ? ? ? }

? ? ? ? continue;

? ? ? }

? ? }

? }

RequestTemplate:構(gòu)建 Request 模版類(lèi)。Options:存放連接、超時(shí)時(shí)間等配置類(lèi)。Retryer:失敗重試策略類(lèi)。

跟蹤代碼發(fā)現(xiàn),首先根據(jù)請(qǐng)求模板RequestTemplate構(gòu)建Request實(shí)例,然后調(diào)用的SynchronousMethodHandler持有的Client的實(shí)例的execute方法。

Client默認(rèn)是通過(guò)FeignRibbonClientAutoConfiguration進(jìn)行注入的:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })

@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",

? ? ? ? matchIfMissing = true)

@Configuration(proxyBeanMethods = false)

@AutoConfigureBefore(FeignAutoConfiguration.class)

@EnableConfigurationProperties({ FeignHttpClientProperties.class })

// Order is important here, last should be the default, first should be optional

// see

// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653

@Import({ HttpClientFeignLoadBalancedConfiguration.class,

? ? ? ? OkHttpFeignLoadBalancedConfiguration.class,

? ? ? ? DefaultFeignLoadBalancedConfiguration.class })

public class FeignRibbonClientAutoConfiguration {

? ? ...

}

如果前面的兩個(gè)配置類(lèi)的條件沒(méi)有滿足,feign.Client 的 IOC 容器實(shí)例沒(méi)有裝配,則:

1. 創(chuàng)建一個(gè) Client.Default 默認(rèn)客戶端實(shí)例,該實(shí)例的內(nèi)部,使用HttpURLConnnection 完成URL請(qǐng)求處理;

2. 創(chuàng)建一個(gè) LoadBalancerFeignClient 負(fù)載均衡客戶端實(shí)例,將 Client.Default 實(shí)例包裝起來(lái),然后返回LoadBalancerFeignClient 客戶端實(shí)例,作為 feign.Client 類(lèi)型的Spring IOC 容器實(shí)例。

@Configuration

class DefaultFeignLoadBalancedConfiguration {

? ? @Bean

? ? @ConditionalOnMissingBean

? ? public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

? ? ? ? ? ? SpringClientFactory clientFactory) {

? ? ? ? return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,

? ? ? ? ? ? ? ? clientFactory);

? ? }

}

LoadBalancerFeignClient 也是一個(gè)feign.Client 客戶端實(shí)現(xiàn)類(lèi)。內(nèi)部先使用 Ribbon 負(fù)載均衡算法計(jì)算server服務(wù)器,然后使用包裝的 delegate 客戶端實(shí)例,去完成 HTTP URL請(qǐng)求處理。

當(dāng)然我們還可以上面兩種,具體配置可以參考Feign、httpclient、OkHttp3 結(jié)合使用:

ApacheHttpClient 類(lèi):內(nèi)部使用 Apache httpclient 開(kāi)源組件完成HTTP URL請(qǐng)求處理的feign.Client 客戶端實(shí)現(xiàn)類(lèi);

OkHttpClient類(lèi):內(nèi)部使用 OkHttp3 開(kāi)源組件完成HTTP URL請(qǐng)求處理的feign.Client 客戶端實(shí)現(xiàn)類(lèi)。

我們調(diào)用feign接口的某一個(gè)方法,最終調(diào)用的LoadBalancerFeignClient.execute()方法。


4.6 LoadBalancerFeignClient#execute

public Response execute(Request request, Request.Options options) throws IOException {

? ? ? ? try {

? ? ? ? ? ? // URL 處理?

? ? ? ? ? ? URI asUri = URI.create(request.url());

? ? ? ? ? ? String clientName = asUri.getHost();

? ? ? ? ? ? URI uriWithoutHost = cleanUrl(request.url(), clientName);

? ? ? ? ? ? FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(

? ? ? ? ? ? ? ? ? ? this.delegate, request, uriWithoutHost);

? ? ? ? ? ? // 獲取調(diào)用服務(wù)配置

? ? ? ? ? ? IClientConfig requestConfig = getClientConfig(options, clientName);

? ? ? ? ? ? // 創(chuàng)建負(fù)載均衡客戶端,執(zhí)行請(qǐng)求

? ? ? ? ? ? return lbClient(clientName)

? ? ? ? ? ? ? ? ? ? .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();

? ? ? ? }

? ? ? ? catch (ClientException e) {

? ? ? ? ? ? IOException io = findIOException(e);

? ? ? ? ? ? if (io != null) {

? ? ? ? ? ? ? ? throw io;

? ? ? ? ? ? }

? ? ? ? ? ? throw new RuntimeException(e);

? ? ? ? }

? ? }

從上面的代碼可以看到,lbClient(clientName) 創(chuàng)建了一個(gè)負(fù)載均衡的客戶端,它實(shí)際上就是生成的如下所述的類(lèi):

public class FeignLoadBalancer extends

? ? ? ? AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse>

熟悉ribbon的朋友應(yīng)該知道AbstractLoadBalancerAwareClient 就是Ribbon負(fù)載均衡調(diào)用的父類(lèi)。具體的負(fù)載均衡實(shí)現(xiàn)策略,可以移步微服務(wù)通信之ribbon實(shí)現(xiàn)原理。至此我們可以得出結(jié)論:feign集成負(fù)載均衡是通過(guò)將FeignLoadBalancer作為調(diào)用feign接口的實(shí)際執(zhí)行者,從而達(dá)到負(fù)載均衡的效果??梢钥吹竭@里與Ribbon高度的解耦,相當(dāng)于我們獲取了服務(wù)名、調(diào)用地址、調(diào)用參數(shù)后,最終交由一個(gè)執(zhí)行器去調(diào)用。執(zhí)行器并不關(guān)心參數(shù)從何而來(lái),這里基于Ribbon提供的執(zhí)行器實(shí)現(xiàn)只是根據(jù)傳遞的服務(wù)名找到了一個(gè)正確的實(shí)例去調(diào)用而已。


五、總結(jié)

到這里Feign的介紹就結(jié)束了,我們使用一張F(tuán)eign遠(yuǎn)程調(diào)用的基本流程總結(jié)一下 Feign 調(diào)用鏈(圖片來(lái)自Feign原理 (圖解)):

最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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