文档章节

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

o
 osc_9gm4ypss
发布于 2019/09/26 22:25
字数 1832
阅读 15
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

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

[TOC]

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 整体装配流程分析

<center><b>图1:Feign 整体装配流程</b></center>

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,实现熔断与限流。


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

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
beego API开发以及自动化文档

beego API开发以及自动化文档 beego1.3版本已经在上个星期发布了,但是还是有很多人不了解如何来进行开发,也是在一步一步的测试中开发,期间QQ群里面很多人都问我如何开发,我的业余时间实在...

astaxie
2014/06/25
2.7W
22
Nutch学习笔记4-Nutch 1.7 的 索引篇 ElasticSearch

上一篇讲解了爬取和分析的流程,很重要的收获就是: 解析过程中,会根据页面的ContentType获得一系列的注册解析器, 依次调用每个解析器,当其中一个解析成功后就返回,否则继续执行下一个解...

强子哥哥
2014/06/26
712
0
程序猿媛一:Android滑动翻页+区域点击事件

滑动翻页+区域点击事件 ViewPager+GrideView 声明:博文为原创,文章内容为,效果展示,思路阐述,及代码片段。文尾附注源码获取途径。 转载请保留原文出处“http://my.oschina.net/gluoyer...

花佟林雨月
2013/11/09
4.2K
1
C++的JSON解析类--JSONVALUE

Jsonvalue 是 C++ 的 JSON 类,用来解析 JSON 到 C++ 对象,也可将对象转成 JSON 字符串。支持 ANSI 和 Unicode。特点: 严格和松散模式 不同数据类型 简单 API 仅依赖 STL 示例代码: JSON...

匿名
2013/03/10
2.9K
1
Base 参数解析库--bash argsparse

bash argsparse 是一个 Bash 的高级参数解析库。 Its purpose is to replace the option parsing and usage describing functions commonly rewritten in all scripts. Its features includ......

匿名
2013/03/27
377
0

没有更多内容

加载失败,请刷新页面

加载更多

Canal binlog 日志 Dump 流程分析

点击上方“中间件兴趣圈”,选择“设为星标” 做积极的人,越努力越幸运! Canal 的 dump 支持串行和并行模式两种模式,本篇重点梳理 dump 的核心流程,以便对 dump 过程有一个充分的了解,更...

中间件兴趣圈
今天
0
0
战地笔记:空降大佬如何烧三把火?

背景交代 某公司KF部门,一线KF几百人,二线三线、经理加内部运营一百多人,研发四五十人。KF主要集中在非一线城市某地(后面记为A地,总部记为B地),异地管理问题较严峻,并且A地大本营里除...

腾哥
08/07
0
0
Reactor 3 (13): 数据收集 collect

有的时候流数据有需要转化为其他类型数据,同Stream相同,Reactor也有将数据进行收集的方法: collect () : 将数据根据给出的collector进行收集 collectList() : 收集收集为list形式 collec...

osc_7cws6vmd
17分钟前
5
0
知识变现:做到这2点,用户一定会为你付费!

本文内容整理自: 线下活动:《如何通过直播实现知识变现?》的其中一个主题。 时间:2020年夏天 地点:广州 · 永庆坊 · 钟书阁 举办:Alpha读书会 作为法学院毕业的Python程序员,看门大叔...

看门大叔
昨天
0
0
HBase/TiDB都在用的数据结构:LSM Tree,不得了解一下?

LSM Tree(Log-structured merge-tree)广泛应用在HBase,TiDB等诸多数据库和存储引擎上,我们先来看一下它的一些应用: 参考 资料【4 】 这么牛X的名单,你不想了解下LSM Tree吗?装X之前,...

Monica2333
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部