Feign 系列(05)Spring Cloud OpenFeign 源码解析

Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/11563952.html#feign)

上一篇 文章中我们分析 Feign 参数解析的整个流程,Feign 原生已经支持 Feign、JAX-RS 1/2 声明式规范,本文着重关注 Spring Cloud 是如果整合 OpenFeign 的,使之支持 Spring MVC?

1. Spring Cloud OpenFeign 最简使用

1.1 引入 maven

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

1.2 @EnableFeignClients 注解扫描包

@SpringBootApplication
@EnableFeignClients // 默认扫描 FeignApplication.class 包下 @FeignClient 注解
public class FeignApplication { public static void main(String[] args) {
SpringApplication.run(FeignApplication.class);
}
}

1.3 @FeignClient 配置

@FeignClient(value = "echo-server",url = "http://127.0.0.1:10010")
public interface EchoService { @GetMapping("/echo/{msg}")
String echo(@PathVariable String msg);
}

总结: 至此,可以像使用普通接口一样调用 http 了

2. Feign 整体装配流程分析

图1:Feign 整体装配流程

sequenceDiagram
participant FeignContext
participant @EnableFeignClients
participant FeignClientsRegistrar
participant FeignClientFactoryBean
participant DefaultTargeter
participant Feign.Builder
FeignContext ->> FeignContext: 1. FeignAutoConfiguration 自动注入
@EnableFeignClients ->> FeignClientsRegistrar: 2. import
FeignClientsRegistrar ->> FeignClientFactoryBean: 3. 扫描 @FeignClient 注解
FeignClientFactoryBean ->> FeignClientFactoryBean: 4. getObject
FeignContext ->> Feign.Builder: 5. 获取Feign.Builder:feign(context)
FeignContext ->> DefaultTargeter: 6. 获取DefaultTargeter:get(context, Targeter.class)
DefaultTargeter ->> Feign.Builder: 7. 创建代理对象:feign.target(target)

总结: OpenFeign 装配有两个入口:

  1. @EnableAutoConfiguration 自动装配(spring.factories)

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
    org.springframework.cloud.openfeign.FeignAutoConfiguration
    • FeignAutoConfiguration 自动装配 FeignContext 和 Targeter,以及 Client 配置。

      • FeignContext 是每个 FeignClient 的装配上下文,默认的配置是 FeignClientsConfiguration
      • Targeter 有两种实现:一是 DefaultTargeter,直接调用 Feign.Builder; 二是 HystrixTargeter,调用 HystrixFeign.Builder,开启熔断。
      • Client :自动装配 ApacheHttpClient,OkHttpClient,装配条件不满足,默认是 Client.Default。但这些 Client 都没有实现负载均衡。
    • FeignRibbonClientAutoConfiguration 实现负载均衡,负载均衡是在 Client 这一层实现的。
      • HttpClientFeignLoadBalancedConfiguration ApacheHttpClient 实现负载均衡
      • OkHttpFeignLoadBalancedConfiguration OkHttpClient实现负载均衡
      • DefaultFeignLoadBalancedConfiguration Client.Default实现负载均衡
  2. @EnableFeignClients 自动扫描

    @EnableFeignClients 注入 FeignClientsRegistrar,FeignClientsRegistrar 开启自动扫描,将包下 @FeignClient 标注的接口包装成 FeignClientFactoryBean 对象,最终通过 Feign.Builder 生成该接口的代理对象。而 Feign.Builder 的默认配置是 FeignClientsConfiguration,是在 FeignAutoConfiguration 自动注入的。

注意: 熔断与限流是 FeignAutoConfiguration 通过注入 HystrixTargeter 完成的,而负载均衡是 FeignRibbonClientAutoConfiguration 注入的。

3. Feign 自动装配

3.1 FeignAutoConfiguration

3.1.1 FeignContext

// FeignAutoConfiguration 自动装配 FeignContext
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>(); @Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}

FeignContext 是每个 Feign 客户端配置的上下文环境,会将初始化一个 Feign 的组件都在一个子 ApplicationContext 中初始化,从而隔离不同的 Feign 客户端。换名话说,不同名称的 @FeignClient 都会初始化一个子的 Spring 容器。

注意: 每个 Feign 客户端除了默认 FeignClientsConfiguration,还可以自定义配置类 FeignClientSpecification,这些配置是如何注入的,会在 @EnableFeignClients 和 @FeignClient 源码分析时具体说明。

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}

总结: FeignClientsConfiguration 是 Feign 的默认配置,可以通过 @EnableFeignClients 和 @FeignClient 修改默认配置。FeignClientsConfiguration 主要配置如下:

@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
// 适配 Spring MVC 注解
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
} @Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
}

3.1.2 Targeter:是否熔断

// FeignAutoConfiguration 自动装配 Targeter
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
} @Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}

总结: Targeter 有两种实现:一是 DefaultTargeter,直接调用 Feign.Builder; 二是 HystrixTargeter,调用 HystrixFeign.Builder,开启熔断。

class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}

3.1.3 Client

// FeignClientsConfiguration 不实现负载均衡的 Client。OkHttpFeignConfiguration 类似
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
}

从装配条件就可以知道,HttpClientFeign 没有实现负载均衡。

3.2 FeignRibbonClientAutoConfiguration

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
// CachingSpringLoadBalancerFactory 用于组装 FeignLoadBalancer
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
}

总结: FeignRibbonClientAutoConfiguration 实现了负载均衡。SpringClientFactory 实际是 RibbonClientFactory,功能等同于 FeignContext,用于装配 Ribbon 的基本组件,SpringClientFactory 这个名称太误导人了。

注意在 FeignRibbonClientAutoConfiguration 之上 import 了三个配置类,HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration。

@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
// cachingFactory 用于组装 FeignLoadBalancer
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}

4. 源码分析

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] basePackages() default {}; // 包扫描路径
Class<?>[] defaultConfiguration() default {};// 默认配置
}

总结: 从 @EnableFeignClients 的属性大致可以推断出,FeignClientsRegistrar 会扫描 basePackages 包下的所有 @FeignClient 注解的类,用 Feign.Builder 生成动态代理的 Bean 注入到 Spring 容器中。是不是这样呢?我们看一下。

4.2.1 FeignClientsRegistrar

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册 @EnableFeignClients#defaultConfiguration 默认配置类
registerDefaultConfiguration(metadata, registry);
// 扫描所有的 @FeignClient 注解
registerFeignClients(metadata, registry);
}
}

(1) registerDefaultConfiguration

registerDefaultConfiguration 最终调用 registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration")) 将 @EnableFeignClients 的默认配置注入到容器中。

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());
}

总结: 还记得 FeignAutoConfiguration 自动装配 FeignContext 时的 List<FeignClientSpecification> configurations 吗,就是将 @EnableFeignClients 和 @FeignClient 的 configuration 属性注册到 Spring 的容器中。

(2) registerFeignClients

registerFeignClients 将 @FeignClient 标注的接口装配成 FeignClientFactoryBean 注入到容器中。FeignClientFactoryBean#getObject 最终会调用 feign.target 生成最终的代理对象。

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader); // 扫描条件: @FeignClient
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
... // 扫描 basePackage 下的 @FeignClient 注解
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注册 @FeignClient 的配置
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 将该接口通过 FeignClientFactoryBean 注入到容器中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
} // 注册 FeignClientFactoryBean
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
...
}

总结: 至此,我们总算看到 Bean 的注册了,但还没有看到 feign.target 生成动态代理。我们知道 FeignClientFactoryBean 是 Spring 的 Bean 工厂类,通过其 getObject 方法可以获取真正的 Bean。所以在 getObject 中一定可以看到类似 feign.target 的代码。

4.2 FeignClientFactoryBean

@Override
public Object getObject() throws Exception {
return getTarget();
} <T> T getTarget() {
// 1. FeignAutoConfiguration 自动装配 FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context); // 2. url不存在,则一定是负载均衡
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
} // 3. url存在,不用负载均衡
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
// 4 FeignAutoConfiguration 自动装配 Targeter
Targeter targeter = get(context, Targeter.class);
// 调用 feign.target 生成动态代理
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}

总结: 至此,@FeignClient 标注的接口,最终通过 targeter.target 生成最终的代理对象。在 FeignClientFactoryBean 中有 2 个重要的对象 FeignClient 和 Targeter,这两个对象都是通过 FeignAutoConfiguration 自动注入的。

  1. FeignClient 是所有 @FeignClient 的上下文环境,管理了 Feign.Builder 的所有配置。根据 @FeignClient(同一个服务)的 contextId 区分不同的上下文环境,每个环境都是一个子 Spring 容器,从而直到隔离不同 @FeignClient 的目的。@FeignClient 的默认配置是 FeignClientsConfiguration,但同时也可以通过 @EnableFeignClients 和 @FeignClient 的 configuration 属性进行修改。

    // NamedContextFactory#getContext 会根据 name 创建一个 ApplicationContext
    // FeignContext.getInstance(this.contextId, type),在本文中就是根据 contextId 区分
    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);
    }
  2. Targeter 可以实现整合 Hystrix,实现熔断与限流。


每天用心记录一点点。内容也许不重要,但习惯很重要!

Feign 系列(05)Spring Cloud OpenFeign 源码解析的更多相关文章

  1. Feign 系列(04)Contract 源码解析

    Feign 系列(04)Contract 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/11563952.html# ...

  2. Java 集合系列 05 Vector详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  3. api网关揭秘--spring cloud gateway源码解析

    要想了解spring cloud gateway的源码,要熟悉spring webflux,我的上篇文章介绍了spring webflux. 1.gateway 和zuul对比 I am the au ...

  4. spring cloud ribbon源码解析(一)

    我们知道spring cloud中restTemplate可以通过服务名调接口,加入@loadBalanced标签就实现了负载均衡的功能,那么spring cloud内部是如何实现的呢? 通过@loa ...

  5. spring cloud ribbon源码解析(二)

    在上一篇文章中主要梳理了ribbon的执行过程,这篇主要讲讲ribbon的负载均衡,ribbon的负载均衡是通过ILoadBalancer来实现的,对ILoadBalancer有以下几个类 1.Abs ...

  6. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  7. Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  8. Java 集合系列 06 Stack详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  9. Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. c# networkcomms 3.0实现模拟登陆总结 转载https://www.cnblogs.com/zuochanzi/p/7039636.html

    最近项目需要做一个客户查询状态系统,当前上位机缺少服务功能,于是找到了networkcomms 开源框架,作为项目使用. 最新版networkcomms 下载地址:https://github.com ...

  2. python学习笔记:接口开发——PythonWEB框架之Flask

    Flask是一个使用 Python 编写的轻量级 Web 应用框架,安装命令如下 pip install flask 一.服务端接口是怎么开发的? 1.启动一个服务 2.接收到客户端传过来的数据3.登 ...

  3. Linux(二)—— Linux配置及指令

    目录 Linux配置及指令 一.linux中常用软件的安装 二.主机名和网络 1.修改主机名 2.设置网络 三.关闭防火墙 1.检查防火墙是否开启 2.清除策略 3.永久关闭第一个防火墙 4.关闭第二 ...

  4. windows 安装虚拟环境

    步骤一:安装virtualenv 在windows cmd终端下使用输入:pip install virtualenv 步骤二:新建虚拟环境 在cmd终端输入virtualenv testvir(环境 ...

  5. java中接口的简单运用&java中的一些异常(运用myeclipse)

    package test;//创建一个名为test的包 public class A4paper implements Paper { public String getSize(){ return& ...

  6. mac 卸载编辑器卸不干净

    Configuration ~/Library/Preferences/ Caches ~/Library/Caches/ Plugins ~/Library/Application Support/ ...

  7. django 内置server 外网不能访问, 报连接超时

    按照官网教程,以 python manage.py runserver 其访问url为 http://127.0.0.1:8000,意味着只能本机访问,而我的django app 部署在 阿里云上面 ...

  8. termcap - 终端功能数据库

    描述 DESCRIPTION termcap 数据库是一个过时 (obsolete) 工具,用来描述以字符为单位的终端和打印机的功能.它之所以被保留,是为了兼容古老的程序:新程序应当使用 termin ...

  9. Codeforces 631E 斜率优化

    题意:给你一个数组,你可以选择数组中的一个数,把它插入数组的其它位置,问∑ i * a[i]的最大值为多少? 思路:设dp[i]表示把第i个数向左边插入可以获得的最大增量,我们假设向左边插入,设插入的 ...

  10. vue指令系统

    一.vue基础 使用vue需在官网上先下载vue.js,网址:https://cn.vuejs.org/v2/guide/installation.html.然后: 在project中引入vue.js ...