Feign 系列(05)Spring Cloud OpenFeign 源码解析
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 整体装配流程
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 装配有两个入口:
@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 的装配上下文,默认的配置是 FeignClientsConfigurationTargeter
有两种实现:一是 DefaultTargeter,直接调用 Feign.Builder; 二是 HystrixTargeter,调用 HystrixFeign.Builder,开启熔断。Client
:自动装配 ApacheHttpClient,OkHttpClient,装配条件不满足,默认是 Client.Default。但这些 Client 都没有实现负载均衡。
FeignRibbonClientAutoConfiguration
实现负载均衡,负载均衡是在 Client 这一层实现的。HttpClientFeignLoadBalancedConfiguration
ApacheHttpClient 实现负载均衡OkHttpFeignLoadBalancedConfiguration
OkHttpClient实现负载均衡DefaultFeignLoadBalancedConfiguration
Client.Default实现负载均衡
@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 自动注入的。
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);
}
Targeter 可以实现整合 Hystrix,实现熔断与限流。
每天用心记录一点点。内容也许不重要,但习惯很重要!
Feign 系列(05)Spring Cloud OpenFeign 源码解析的更多相关文章
- Feign 系列(04)Contract 源码解析
Feign 系列(04)Contract 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/11563952.html# ...
- Java 集合系列 05 Vector详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- api网关揭秘--spring cloud gateway源码解析
要想了解spring cloud gateway的源码,要熟悉spring webflux,我的上篇文章介绍了spring webflux. 1.gateway 和zuul对比 I am the au ...
- spring cloud ribbon源码解析(一)
我们知道spring cloud中restTemplate可以通过服务名调接口,加入@loadBalanced标签就实现了负载均衡的功能,那么spring cloud内部是如何实现的呢? 通过@loa ...
- spring cloud ribbon源码解析(二)
在上一篇文章中主要梳理了ribbon的执行过程,这篇主要讲讲ribbon的负载均衡,ribbon的负载均衡是通过ILoadBalancer来实现的,对ILoadBalancer有以下几个类 1.Abs ...
- Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 06 Stack详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
随机推荐
- Hexo next博客的pjax一个Bug引发的关于pjax用法的小技巧-----pjax后图片点击放大的js失效
文章目录 广告: 背景 发现 解决 get技能 广告: 本人博客地址:https://mmmmmm.me 源码:https://github.com/dataiyangu/dataiyangu.git ...
- 《node.js开发指南》partial is not defined的解决方案
由于ejs的升级,<node.js开发指南>中使用的 partial 函数已经摒弃,使用foreach,include代替 原来的代码是: <%- partial('listitem ...
- Apache Hadoop集群离线安装部署(一)——Hadoop(HDFS、YARN、MR)安装
虽然我已经装了个Cloudera的CDH集群(教程详见:http://www.cnblogs.com/pojishou/p/6267616.html),但实在太吃内存了,而且给定的组件版本是不可选的, ...
- Java 注解处理器
在Android开发中常用到一些第三方注解框架来简化代码.加快开发,如ButterKnife,androidannotations. 虽然常会用到,但没研究过其生成代码的原理,趁年后不忙,又温习了注解 ...
- 视频专家之路【二】:ffmpeg工具的使用
本文是听了雷宵骅大神的课之后的总结,部分内容借用了其PPT的内容,如有侵权请告知删除. 雷宵骅大神的博客为:https://blog.csdn.net/leixiaohua1020 要学习汽修,那么首 ...
- Python删除文件夹
import os os.rmdir('OS-Demo-2') os.removedirs('OS-Demo-3/sub-Dir-1') os.removedirs()会自动将上一级文件夹也删除,谨慎 ...
- Java中的内部类怎么用
一.为什么需要内部类?java内部类有什么好处?为什么需要内部类? 首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时 ...
- Jenkins使用admin修改安全矩阵的时候报错“admin没有Overall/Read权限”
1.注册完Jenkins,然后使用admin用户登录,报错“admin没有Overall/Read权限”: 2.因为我自己的Jenkins是放在tomcat/webapps的目录下的,所以Jenkin ...
- leetcode-161周赛-1250-检查好数组
题目描述: 唯一的结论是如果数组中所有数的最大公约数为 1,则存在解,否则不存在.所以只需要计算所有数最大公约数即可,时间复杂度O(nlog(m)),其中 m 为数字大小. class Solutio ...
- flex: 1在ios10.2系统手机端的换行布局失败问题
使用flex:1要追加flex-basis: auto;可以简写flex: 1 1 auto; 表格不可以用flex布局