[SpringCloud] Feign Client 的创建 (二) (五)

news2024/11/28 6:52:54

文章目录

      • 1.自动配置FeignAutoConfiguration
      • 2.生成 Feign Client
        • 2.1 从Feign Client子容器获取组件
        • 2.2 Feign Client子容器的创建
        • 2.3 构建Feign Client实例

1.自动配置FeignAutoConfiguration

spring-cloud-starter-openfeign 包含了 spring-cloud-openfeign-core

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

FeignAutoConfiguration:

在这里插入图片描述

  • FeignClientSpecification: FeignClient的配置类。
  • FeignContext: Spring容器中所有的FeignClient规范类实例都放入了FeignContext。其中存在两个map。

在这里插入图片描述

2.生成 Feign Client

FeignClientFactoryBean: 就是Spring的FactoryBean。

在这里插入图片描述

在这里插入图片描述

2.1 从Feign Client子容器获取组件

FeignClientFactoryBean.getObject():

//FeignClientFactoryBean.java
public Object getObject() throws Exception {
	return getTarget();
}
//FeignClientFactoryBean.java
<T> T getTarget() {
	//根据spring容器,获取FeignContext,Feign的上下文,也是FeignClient的工厂类
	FeignContext context = this.applicationContext.getBean(FeignContext.class);
	//根据FeignContext,获取一个Feign的构建器
	Feign.Builder builder = feign(context);
	...
	
	return (T) targeter.target(this, builder, context,
			new HardCodedTarget<>(this.type, this.name, url));
}
  1. 根据spring容器, 获取FeignContext, 是FeignClient的工厂类。
  2. 根据FeignContext, 获取Feign的构造器。

在这里插入图片描述

feign: 从Feign Client子容器获取组件。

//FeignClientFactoryBean.java
protected Feign.Builder feign(FeignContext context) {
	//get方法:从FeignContext中获取对应类型的实例,底层会从当前FeignClient对应的子容器中获取
	
	//这里获取Feign的日志工厂
	FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
	Logger logger = loggerFactory.create(this.type);

	// @formatter:off
	//这里获取Feign的构建器
	//构建器的意义我们不需要关注复杂的构建流程,只需要给构建器传递一些需要的组件即可
	//这里主要往构建器放入一些FeignClient依赖的一些组件
	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
	//获取FeignClientProperties进行一些属性的配置
	configureFeign(context, builder);

	return builder;
}

//看其中一个get方法:
//FeignClientFactoryBean.java
protected <T> T get(FeignContext context, Class<T> type) {
	//注意,当前类是FeignClientFactoryBean
	//所以这个this.contextId实际上是当前FeignClient的服务id、微服务名称
	T instance = context.getInstance(this.contextId, type);
	if (instance == null) {
		throw new IllegalStateException(
				"No bean found of type " + type + " for " + this.contextId);
	}
	return instance;
}

在这里插入图片描述

//NamedContextFactory.java,就是FeignContext.java
public <T> T getInstance(String name, Class<T> type) {
	//根据name先获取对应的子容器
	//name就是微服务名称,FeignClient的名称
	AnnotationConfigApplicationContext context = getContext(name);
	//根据类型从当前子容器,和子容器所有的祖先容器中查找bean的名称,判断是否存在
	if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
			type).length > 0) {
		//存在就返回对应类型的实例
		return context.getBean(type);
	}
	return null;
}
2.2 Feign Client子容器的创建

获取子容器, 如果获取不到的话则创建子容器。

getContext -> createContext:

在这里插入图片描述

//NamedContextFactory.java 
//FeignContext.java继承自NamedContextFactory.java
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);
}

//创建子容器
//NamedContextFactory.java
protected AnnotationConfigApplicationContext createContext(String name) {
	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
	//这里configurations存的就是各个feign client的规范类
	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()) {
		//default开头的是全局的规范类,存的是@EnableFeignClients的defaultConfiguration属性配置的配置类
		if (entry.getKey().startsWith("default.")) {
			for (Class<?> configuration : entry.getValue().getConfiguration()) {
				//将全局的配置类注册到该容器
				context.register(configuration);
			}
		}
	}
	
	//注册占位符配置解析器,可以解析bean定义属性值和{@code @Value}注解中的占位符。
	//注册默认配置类,defaultConfigType就是FeignClientsConfiguration.class
	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) {
		// 关键!为当前容器设置父容器
		context.setParent(this.parent);
		context.setClassLoader(this.parent.getClassLoader());
	}
	context.setDisplayName(generateDisplayName(name));
	//刷新容器
	context.refresh();
	return context;
}

在这里插入图片描述

采用DCL锁来控制单例。

2.3 构建Feign Client实例

FeignClientFactoryBean.getObject():

//FeignClientFactoryBean.java
public Object getObject() throws Exception {
	return getTarget();
}

//FeignClientFactoryBean.java
<T> T getTarget() {
	//根据spring容器,获取FeignContext,Feign的上下文,也是FeignClient的工厂类
	FeignContext context = this.applicationContext.getBean(FeignContext.class);
	//根据FeignContext,获取一个Feign的构建器
	//底层就是从当前feignClient名称对应的子容器中获取一些
	//  创建FeignClient所依赖的组件实例
	Feign.Builder builder = feign(context);
	//判断是否指定url属性,没有指定了就会负载均衡的方式进行远程调用
	if (!StringUtils.hasText(this.url)) {
		//为服务名补全协议
		if (!this.name.startsWith("http")) {
			this.url = "http://" + this.name;
		}
		else {
			this.url = this.name;
		}
		//拼接前缀,就是path属性,cleanPath会先格式化一下
		this.url += cleanPath();
		//没有指定url,使用具有负载均衡的远程调用客户端 构建feignClient
		return (T) loadBalance(builder, context,
				new HardCodedTarget<>(this.type, this.name, this.url));
	}
	//指定了url,则是直连方式
	if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
		//补全协议
		this.url = "http://" + this.url;
	}
	//拼接前缀,就是path属性,cleanPath会先格式化一下
	String url = this.url + cleanPath();
	//getOptional:也是从context中对应的feignClient名称的子容器中获取Client类型的实例
	//这个Client就是发起远程调用的客户端
	Client client = getOptional(context, Client.class);
	if (client != null) {
		//判断client是否是具有负载均衡的功能client,如果是的话取消包装
		//确保直连
		if (client instanceof LoadBalancerFeignClient) {
			// ribbon的负载均衡客户端
			// not load balancing because we have a url,
			// but ribbon is on the classpath, so unwrap
			// 没有负载平衡,因为我们有一个URL,但是ribbon在类路径中,所以请取消包装
			client = ((LoadBalancerFeignClient) client).getDelegate();
		}
		if (client instanceof FeignBlockingLoadBalancerClient) {
			// openFeign的负载均衡客户端
			// not load balancing because we have a url,
			// but Spring Cloud LoadBalancer is on the classpath, so unwrap
			// 因为我们有一个URL,所以没有负载均衡
			// 但是Spring Cloud LoadBalancer在类路径上,因此请取消包装
			client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
		}
		builder.client(client);
	}
	//从子容器获取对应类型的实例
	Targeter targeter = get(context, Targeter.class);
	//直连方式创建
	return (T) targeter.target(this, builder, context,
			new HardCodedTarget<>(this.type, this.name, url));
}

在这里插入图片描述

没有指定url,使用具有负载均衡的远程调用客户端 构建feignClient。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

//HystrixTargeter.java
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
		FeignContext context, Target.HardCodedTarget<T> target) {
	if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
		//没有开启熔断功能话就不是熔断的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);
	}
	//也是调feign.target
	return feign.target(target);
}
  1. 没有开启熔断功能话就不是熔断的Builder。
  2. 如果开启了熔断,就会处理一些服务降级的配置。
//Feign.java
public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

在这里插入图片描述

ReflectiveFeign.newInstance():

//ReflectiveFeign.java
public <T> T newInstance(Target<T> target) {
  //targetToHandlersByName.apply:生成方法处理器
  //返回值nameToHandler:
  //  key:当前feignClient的方法名
  //  value:方法处理器
  Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
  //methodToHandler:key是方法对象,value是方法处理器
  Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
  //默认方法处理器列表
  List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
  //遍历当前feignClient的接口的所有的方法
  for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      //Object的方法不处理
      continue;
    } else if (Util.isDefault(method)) {//是否是接口中的默认方法
      //默认方法创建一个默认方法处理器
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      //添加到默认方法处理器集合
      defaultMethodHandlers.add(handler);
      //保存方法和处理器映射关系
      methodToHandler.put(method, handler);
    } else {
      //不是默认方法,就是抽象方法
      //从nameToHandler获取已经生成好的对应的方法处理器
      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
  }
  //jdk动态代理,创建InvocationHandler,再创建代理对象
  InvocationHandler handler = factory.create(target, methodToHandler);
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class<?>[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

通过jdk动态代理, 创建InvocationHandler, 再创建代理对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1560565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决echarts xAxis设置type:‘value‘后 x轴有负值的时候 Y轴在0点显示

前提&#xff1a;xAxis设置type:‘value’ 数据&#xff1a;data里面含有负数值&#xff0c;导致Y坐标轴一直在 X&#xff08;0&#xff09;上面显示 解决方案&#xff1a; yAxis里面设置 axisLine: { onZero:false } yAxis:{type: value,name:测试,axisLine: { onZero:false …

Rust 机器学习图形库 petgraph

一、介绍 Petgraph 是一个开源的图数据结构库&#xff0c;提供了非常丰富的图形类型和算法&#xff0c;并且支持将图形以 Graphviz 格式输出&#xff0c;还允许你为图的节点和边赋予任意类型的数据&#xff0c;从而能够灵活地处理和表示复杂的数据关系。 Petgraph 支持边的方…

【Linux多线程】生产者消费者模型

【Linux多线程】生产者消费者模型 目录 【Linux多线程】生产者消费者模型生产者消费者模型为何要使用生产者消费者模型生产者消费者的三种关系生产者消费者模型优点基于BlockingQueue的生产者消费者模型C queue模拟阻塞队列的生产消费模型 伪唤醒情况&#xff08;多生产多消费的…

MySQL数据库----------探索高级SQL查询语句 (二)

目录 一、子查询 1.1多表查询 1.2多层嵌套 1.3 insert语句子查询 1.4update语句子查询 1.5delete语句子查询 1.6EXISTS 1.7子查询&#xff0c;别名as 二、mysql视图 2.1mysql视图介绍 2.2mysql作用场景[图]: 2.3视图功能&#xff1a; 2.4视图和表的区别和联系 区别…

Linux 个人笔记之三剑客 grep sed awk

文章目录 零、预一、grep 文本过滤工具基础篇实战篇 二、sed 字符流编辑器基础篇实战篇 三、awk 文本处理工具基础篇实战篇 四、附xargsuniq & sort基础篇实战篇 cut 零、预 bash 的命令行展开 {} $ echo file_{1..4} file_1 file_2 file_3 file_4$ echo file_{a..d} file_…

【攻防世界】unseping (反序列化与Linux bash shell)

打开题目环境&#xff1a; 1、进行PHP代码审计&#xff0c;通过审计得知需要用到PHP反序列化。找到输出flag的位置为 ping()函数。通过使用 exec() 函数来执行 $ip 并将结果保存在 $result 中&#xff0c;最终输出 $result。 2、接着寻找给 $ip 传参的位置&#xff0c;发现通过…

FastEI论文阅读

前言 研究FastEI有很长时间了&#xff0c;现在来总结一下&#xff0c;梳理一下认知。论文地址&#xff1a;https://www.nature.com/articles/s41467-023-39279-7&#xff0c;Github项目地址&#xff1a;https://github.com/Qiong-Yang/FastEI。 概要 这篇文章做的工作是小分子…

16.面向对象的软件测试技术

主要考点&#xff1a; 1、面向对象相关的基础概念&#xff1b;&#xff08;已经在软件工程的课程中讲过&#xff0c;要熟悉UML图&#xff0c;知道类和类之间的关系&#xff0c;这些知识也可能结合到下午题考察&#xff09; 2、面向对象的软件测试技术&#xff1b;&#xff08;大…

基于单片机汽车超声波防盗系统设计

**单片机设计介绍&#xff0c;基于单片机汽车超声波防盗系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机汽车超声波防盗系统设计概要主要涉及利用超声波传感器和单片机技术来实现汽车的安全防盗功能。以下是对…

辽宁梵宁教育:点亮大学生设计技能之光

辽宁梵宁教育作为专注于设计教育的线上机构&#xff0c;对大学生设计技能的提升和就业前景产生了深远的影响。在当前数字化时代&#xff0c;设计技能已逐渐成为各行各业不可或缺的重要能力&#xff0c;而梵宁教育正是抓住了这一机遇&#xff0c;致力于培养具备创新思维和实践能…

还在问如何入门 Python 爬虫?收藏这篇直接带你上路爬虫!!

“入门”是良好的动机&#xff0c;但是可能作用缓慢。如果你手里或者脑子里有一个项目&#xff0c;那么实践起来你会被目标驱动&#xff0c;而不会像学习模块一样慢慢学习。另外如果说知识体系里的每一个知识点是图里的点&#xff0c;依赖关系是边的话&#xff0c;那么这个图一…

Java毕业设计-基于springboot开发的致远汽车租赁系统平台-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、管理员功能模块3、业务员功能模块3、用户功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot…

LC 106.从中序与后序遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a; inorder [9,3,15,20,7], post…

STM32G系 编程连接不上目标板,也有可能是软件不兼容。

由于一直用的老版本STM32 ST-LINK Utility 4.20 &#xff0c;找遍了所有问题&#xff0c;SWD就是连不上目标板。 电源脚 VDDA 地线&#xff0c;SWD的四条线&#xff0c;还是不行&#xff0c;浪费了一天&#xff0c;第二天才想起&#xff0c;是不是G系升级了 SWD协议。结果下载…

安全访问多线程环境:掌握 Java 并发集合的使用技巧

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

[优选算法专栏]专题十五:FloodFill算法(二)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

【Vue3】el-checkbox-group实现权限配置和应用

一. 需求 针对不同等级的用户&#xff0c;配置不同的可见项 配置效果如下 &#xff08;1&#xff09;新增&#xff0c;获取数据列表 &#xff08;2&#xff09;编辑&#xff0c;回显数据列表 应用效果如下 &#xff08;1&#xff09;父级配置 &#xff08;2&#xff09;子级…

leetcode90. 子集 II

去重逻辑&#xff1a; 关键是画出递归树&#xff01;当我们即将进入第二个2的递归的时候&#xff0c;发现isVisit数组是100&#xff0c;也就是说这俩重复的数是False&#xff0c;并且这俩在nums值相同&#xff0c;所以写出去重逻辑&#xff01; class Solution { public:vector…

2024-2028年中国导电滑环市场行情及未来发展前景研究报告

导电滑环应用领域广泛 全球市场将保持增长趋势 导电滑环又称为集流环、集电环、导电环&#xff0c;是一种电气连接器件&#xff0c;用于在旋转部件和静止部件之间传输电能信号。导电滑环避免了传统导线在旋转中存在的磨损和扭伤&#xff0c;可提高机器运转效率和稳定性&#xf…

美易官方:通胀持续降温,美联储可能在6月份降息

近期&#xff0c;LPL首席经济学家在接受采访时表示&#xff0c;通胀持续降温&#xff0c;美联储可能在6月份降息。这一消息引起了市场的广泛关注和讨论。通胀一直是全球经济面临的难题之一&#xff0c;而美联储的货币政策也一直是市场关注的焦点。那么&#xff0c;通胀降温和美…