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

文章目录

      • 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.mfbz.cn/a/498439.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS实现小车旅行动画实现

小车旅行动画实现 效果展示 CSS 知识点 灵活使用 background 属性下的 repeating-linear-gradient 实现路面效果灵活运用 animation 属性与 transform 实现小车和其他元素的动画效果 动画场景分析 从效果图可以看出需要实现此动画的话&#xff0c;需要position属性控制元素…

机器学习(三)

神经网络: 神经网络是由具有适应性的简单单元组成的广泛并行互连的网络&#xff0c;它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。 f为激活(响应)函数: 理想激活函数是阶跃函数&#xff0c;0表示抑制神经元而1表示激活神经元。 多层前馈网络结构: BP(误差逆…

微服务demo(二)nacos服务注册与集中配置

环境&#xff1a;nacos1.3.0 一、服务注册 1、pom&#xff1a; 移步spring官网https://spring.io&#xff0c;查看集成Nacos所需依赖 找到对应版本点击进入查看集成说明 然后再里面找到集成配置样例&#xff0c;这里只截一张&#xff0c;其他集成内容继续向下找 我的&#x…

【Python】python+requests+excel+unittest+ddt实现接口自动化实例

目录 测试需求实现思路框架代码实例1. 环境准备和配置文件2. Excel接口数据及测试结果3. API封装4. 读取Excel数据5. 测试用例6. 日志和配置文件处理7. HTMLTestRunner生成可视化的html报告8. 报告通过飞书/邮件发送报告通过飞书发送报告通过邮件发送9. 入口函数10. 飞书Webhoo…

Day46:WEB攻防-注入工具SQLMAPTamper编写指纹修改高权限操作目录架构

目录 数据猜解-库表列数据&字典 权限操作-文件&命令&交互式 提交方法-POST&HEAD&JSON 绕过模块-Tamper脚本-使用&开发 分析拓展-代理&调试&指纹&风险&等级 知识点&#xff1a; 1、注入工具-SQLMAP-常规猜解&字典配置 2、注入…

Nagios工具

一 nagios 相关概念 Nagios 是一款开源的免费网络监视工具&#xff0c;能有效监控 Windows、Linux 和 Unix 的主机状态&#xff0c;交换机路由器等网络设置&#xff0c;打印机等。在系统或服务状态异常时发出邮件或短信报警第 一时间通知网站运维人员&#xff0c;在状态恢复后…

33.HarmonyOS App(JAVA)鸿蒙系统app数据库增删改查

33.HarmonyOS App(JAVA)鸿蒙系统app数据库增删改查 关系数据库 关系对象数据库&#xff08;ORM&#xff09; 应用偏好数据库 分布式数据库 关系型数据库&#xff08;Relational Database&#xff0c;RDB&#xff09;是一种基于关系模型来管理数据的数据库。HarmonyOS关系型…

构建图书管理系统:使用Python的Tkinter和PIL模块

本博客将介绍如何使用Python中的Tkinter库和PIL&#xff08;Python Imaging Library&#xff09;模块构建一个简单的图书管理系统。图书管理系统是一个常见的应用程序&#xff0c;用于管理图书馆或个人收藏的图书信息。我们将逐步展示系统的功能&#xff0c;包括添加图书、查询…

【机器学习】包裹式特征选择之序列后向选择法

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

AJAX-项目优化(目录、基地址、token、请求拦截器)

目录管理 基地址存储 在utils/request.js配置axios请求基地址 作用&#xff1a;提取公共前缀地址&#xff0c;配置后axios请求时都会baseURLurl 填写API的公共前缀后&#xff0c;将js文件导入到html文件中 <script src"../../utils/request.js"></script&…

docker-compose mysql

使用docker-compose 部署 MySQL&#xff08;所有版本通用&#xff09; 一、拉取MySQL镜像 我这里使用的是MySQL8.0.18&#xff0c;可以自行选择需要的版本。 docker pull mysql:8.0.18二、创建挂载目录 mkdir -p /data/mysql8/log mkdir -p /data/mysql8/data mkdir -p /dat…

Django安装及第一个项目

1、安装python C:\Users\leell>py --version Python 3.10.6 可以看出我的环境python的版本3.10.6&#xff0c;比较新 2、 Python 虚拟环境创建 2.1 官网教程 目前&#xff0c;有两种常用工具可用于创建 Python 虚拟环境&#xff1a; venv 在 Python 3.3 及更高版本中默…

yarn的安装和使用:Yarn 快速上手指南

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Ubuntu18.04 下Ublox F9P 实现RTK (利用CORS服务无需自建基站)

本内容参考如下连接:Ubuntu下Ublox F9P利用CORS服务无需自建基站实现RTK-CSDN博客 一、Ublox F9P 硬件模块示意图 图中展示了Ublox F9P的接口,包括串口2(`UART1`和`UART2`),USB1。需要人为通过u-center(Ublox F9P的显示软件)软件设置以下功能: Ublox通过`UART1`向PC端发送…

enscan自动化主域名信息收集

enscan下载 Releases wgpsec/ENScan_GO (github.com) 能查的分类 实操&#xff1a; 首先打开linux 的虚拟机、 然后把下面这个粘贴到虚拟机中 解压后打开命令行 初始化 ./enscan-0.0.16-linux-amd64 -v 命令参数如下 oppo信息收集 运行下面代码时 先去配置文件把coo…

|行业洞察·地产|《2023年中国物流地产行业报告-23页》

报告内容的详细解读&#xff1a; 1. 宏观经济背景 GDP增长&#xff1a;2023年二季度&#xff0c;中国国民生产总值&#xff08;GDP&#xff09;同比实际增长率为6.3%&#xff0c;显示出弱复苏态势&#xff0c;但增速较2022年第二季度有所下降。消费市场&#xff1a;消费市场同…

睿尔曼超轻量仿人机械臂之复合机器人底盘介绍及接口调用

机器人移动平台是一个包含完整成熟的感知、认知和定位导航能力的轮式机器人底盘产品级平台&#xff0c;产品致力于为各行业细分市场的商用轮式服务机器人提供一站式移动机器人解决方案&#xff0c;让合作伙伴专注在核心业务/人机交互的实现。以下是我司产品双臂机器人以及复合升…

九泰智库 | 医械周刊- Vol.18

⚖️ 法规动态 医疗器械并购风起&#xff0c;深交所联合北京经信局举办医疗器械座谈交流会 | 第一财经 近日&#xff0c;深圳证券交易所联合北京市经济和信息化局&#xff0c;举办“发挥深市医疗器械龙头引领作用&#xff0c;积极培育地方新质生产力”座谈交流活动。邀请了行业…

Linux 安装部署高性能缓存服务redis

Linux 系统安装Redis 5 注意事项&#xff1a; 下载Redis 文件包&#xff0c;并上传至linux服务上解压 tar -zxvf redis.tar安装&#xff1a; 编译 make PREFIX/usr/local/redis install配置&#xff1a; redis.conf daemonize yes bind 127.0.0.1 192.168.1.221 supervised…

TQ-DDL contention事件导致数据库hang死

数据库一天内多次hang住&#xff0c;最后只能重启恢复&#xff0c;操作系统及数据库版本&#xff1a;Windows Oracle 12.2.0.1检查hang住时间段alert日志&#xff0c;发现数据库多次重启日志&#xff0c;基本上是hang住然后手工重启。检查ash记录&#xff0c;发现重启前有很多“…
最新文章