當(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原理 (圖解)):
