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. ROS编程: 重要的代码优化知识点记录(1)

    订阅多个话题并对其进行同步处理 本小节针对在ROS节点中需要订阅两个及两个以上的话题时,需要保持对这两个话题数据的同步,且需要同时接收数据一起处理然后当做参数传入到另一个函数中: 研究背景:reals ...

  2. python作业/练习/实战:2、注册、登录(文件读写操作)

    作业要求 1.实现注册功能输入:username.passowrd,cpassowrd最多可以输错3次3个都不能为空用户名长度最少6位, 最长20位,用户名不能重复密码长度最少8位,最长15位两次输入 ...

  3. CMS 开发全过程介绍

    1.Web项目开发的一般流程 a) 需求确定 b) 需求分析 i. 架构分析和设计 ii. 业务逻辑分析和设计 iii. 界面设计 iv. 数据库的设计 c) 开发环境搭建 d) 开发和测试 e) 文 ...

  4. Javascript高级程序设计--读书笔记之面向对象(二)

    前面讲了面向对象的封装,这章我们就来说一说继承 1.原型链 实现原型链有一种基本模式,其代码大概如下 <script> function SuperType(){ this.propert ...

  5. react 16.3+ 新生命周期

    react 16.3版本出现了两个新的生命周期函数,并将逐渐废弃componentWillMount().componentWillReceiveProps().componentWillUpdate ...

  6. translation

    *过渡写到本体上(谁做动画写谁身上) transition transition-property  规定应用过渡的CSS属性的名称. transition-duration  定义过渡效果花费的时间 ...

  7. linux上安装php phpredis扩展

    linux 下安装redis以及php Redis扩展 环境配置: centos6.0 nginx/1.0.0 php/5.3.8 mysql/5.5.17 步骤一.下载redis 可以去http:/ ...

  8. Dubbo---注册中心

    1.Multicast 注册中心 1.1 Multicast 注册中心   不需要启动  任何中心节点,只要广播地址一样,就可以互相发现. 1.2 1.3 配置   2.zookeeper 注册中心( ...

  9. C之输入输出函数(1) -- fgets()

    https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/rtref/fgets.htm #include <stdio.h> ...

  10. 经典排序背包——cf1203F

    先把收益为正数的处理掉:策略是挨个扫,扫n遍,碰到能买的就买,然后可以得到一个更新后的r 剩下的就看做是一个背包模型:物品(a,b)表示当背包体积>a时才能装下体积为b的该物品,问最多装几个 无 ...