springcloud-openFeign简单梳理

news2024/11/24 23:23:14

OpenFeign

  openFeign是springcloud中,服务间进行调用的常用方式。了解它,可以更好的处理服务间调用问题。

@EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

  项目模块装配Feign相关。
重点关注。@Import。导入要给FeignClientsRegistrar类。

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

  FeignClientsRegistrar类中实现接口ImportBeanDefinitionRegistrar,通过实现方法registerBeanDefinitions。完成@FeignClient注解相关类的注入到ioc容器。

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	registerDefaultConfiguration(metadata, registry);
	registerFeignClients(metadata, registry);
}

@FeignClient相关类的扫描,注入

  只记录常用的方式,其他可以看源码。这里只关注通过组件扫描注入。组件扫描用到类
ClassPathScanningCandidateComponentProvider,可以重写类的isCandidateComponent方法。完成相关组件的扫描,并封装为beanDefinition。

protected ClassPathScanningCandidateComponentProvider getScanner() {
		return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
			@Override
			protected boolean isCandidateComponent(
					AnnotatedBeanDefinition beanDefinition) {
				boolean isCandidate = false;
				if (beanDefinition.getMetadata().isIndependent()) {
					if (!beanDefinition.getMetadata().isAnnotation()) {
						isCandidate = true;
					}
				}
				return isCandidate;
			}
		};
	}

  在openFeign中,只是扫描过滤了,非注解等相关类。通过设置IncludeFilter过滤,过滤类型是注解过滤FeignClient

ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);
		scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
		Set<String> basePackages = getBasePackages(metadata);
		for (String basePackage : basePackages) {
			candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
		}

  扫描范围为,通过方法getBasePackages获取跟包。逻辑主要是,标注注解EnableFeignClients的属性value,basePackages,basePackageClasses配置的内容。如果未配置,默认为当前类所在的包。我的理解是标注注解EnableFeignClients的类的包。

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
		Map<String, Object> attributes = importingClassMetadata
				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

		Set<String> basePackages = new HashSet<>();
		for (String pkg : (String[]) attributes.get("value")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (String pkg : (String[]) attributes.get("basePackages")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

		if (basePackages.isEmpty()) {
			basePackages.add(
					ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

  扫描并返回对应类的beanDefinition后,遍历。获取注解FeignClient上的属性信息,通过BeanDefinitionBuilder构建类型是FeignClientFactoryBean的beanDefinition。并注册到BeanDefinitionRegistry中。FeignClientFactoryBean是一个工厂bean,可以通过getObject获取到@FeignClient注解代理的Bean。

FeignClientFactoryBean

  上面介绍的,注解@FeignClient标注的类,都被封装为FeignClientFactoryBean类型的beadDefinition注册到IOC。接下来看看,通过FeignClientFactoryBean如何获取到注解@FeignClient的代理对象,完成方法的调用。

// 通过getObject获取client对象。
@Override
public Object getObject() {
	return getTarget();
}
<T> T getTarget() {
		FeignContext context = beanFactory != null
				? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
       // @FeignClient注解中url属性不存在的业务逻辑
		if (!StringUtils.hasText(url)) {

			if (LOG.isInfoEnabled()) {
				LOG.info("For '" + name
						+ "' URL not provided. Will try picking an instance via load-balancing.");
			}
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
		}

       // @FeignClient注解中url属性存在的业务逻辑
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client)
						.getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));
	}

contexts,每一个@FeignClient标注的接口都有自己的应用上下文的理解(下面会用到)

   通过工厂类获取目标对象的时候,首先获取FeignContext对象。FeignContext是用于创建和管理Feign Client所依赖的各种类的工厂类
  每个Feign Client会关联一个AnnotationConfigApplicationContext实例,用于存取Feign Client所依赖的各种类的实例
  (1)configurations中有根据name配置到的配置类,注册到AnnotationConfigApplicationContext。name = contextId-->value-->name-->serviceId(优先级从前到后,哪个属性不为空) + "FeignClientSpecification";
  (2)default默认的配置类,注册到AnnotationConfigApplicationContext。默认的配置类是@EnableFeignClients注解中属性defaultConfiguration中配置的配置类
  (3)注册PropertyPlaceholderAutoConfiguration
  (4)设置parent为当前服务应用上下文
  (5)刷新应用上下文

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		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()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		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) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

  注册AnnotationConfigApplicationContext,首先是注册服务接口的配置类,其次是注册全局的配置类,最后是注册默认的配置类defaultConfigType。defaultConfigType是FeignContext类实例化默认传入的FeignClientsConfiguration.class。
FeignClientsConfiguration
  每个FeignClient注解标注的接口,对应的AnnotationConfigApplicationContext属性,allowEagerClassLoading = true。也就是说后加载的允许覆盖前面加载的类。
在这里插入图片描述

configurations

  configurations中保存了每个Feign Client所依赖的配置类,在创建AnnotationConfigApplicationContext的过程中,这些配置类会被注入到Bean工厂中。

Feign.Builder

  用于构建feign对象。

  1. 通过ContextId获取到对应的AnnotationConfigApplicationContext实例。获取Feign.Builder对象。
  2. Feign.Builder的build方法创建feign对象ReflectiveFeign
  3. ReflectiveFeign对象的newInstance方法返回的就是一个代理对象,代理对象的InvocationHandler实现类就是ReflectiveFeign对象的内部类FeignInvocationHandler。

@FeignClient中url属性值:存在

  // @FeignClient注解中url属性存在的业务逻辑
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client)
						.getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));

拼接请求路径。

  通过url,path拼接路径。lg:http://… + /path;

获取client

  通过方法getOptional获取client。

Client client = getOptional(context, Client.class);
  1. 通过FeignContext的getInstance方法,获取到contextId对应的应用上下文applicationContext。
  2. 在这个应用上下文中获取到类型为Client.class的bean对象。并返回对象。
  3. 根据client所实现的接口类型,转换为对应的类型。
  4. 相同的方式,通过contextId获取类型为Targeter.class的bean对象。
  5. 调用Targeter的target方法,默认是调用Feign.build的build()方法创建ReflectiveFeign对象,调用对象的newInstance方法,创建代理对象。返回代理@FeignClient标注接口的代理对象。

  方法执行,调用代理对象ReflectiveFeign中的内部类FeignInvocationHandler的invoke方法。根据Method找到对应的MethodHandler。默认实现是SynchronousMethodHandler。

  1. 方法之前前,通过RequestInterceptor对请求进行拦截处理。
  2. 调用client.execute方法,请求调用。
  3. 结果封装

@FeignClient中url属性值:不存在

  @FeignClient中url为空,则url默认 = http:// + name + path;
  通过loadBalance方法构建请求代理对象。loadBalance。

loadBalance

  1. 通过FeignContext,获取到当前contextId对应的applicationContext。
  2. 通过applicationContext获取类型为client.class的实现类。
  3. 通过Feign.build设置client为通过applicationContext获取到的client。
  4. 通过applicationContext获取类型为Targeter.class的实现类。
  5. 调用Targeter.target方法获取到代理对象。

配置文件

feign: 
 client: 
   defaultToProperties: false # 是否以配置文件中配置为主。默认为true
   config: #对应FeignClientProperties类的config成员变量
    default: 
      # 日志级别
      logger-level: BASIC
      # 超时时间
      connect-timeout: 10000

在这里插入图片描述
  将config下的配置封装为config = Map<String,FeignClientConfiguration>。FeignClientConfiguration封装上面的配置。lg:日志级别,超时时长。Map中的key为@FeignClient注解属性contextId。默认为default,也可以为没有给Feign接口配置对应的参数。
  配置文件中的参数优先级别。feign接口级别的配置 --> 默认配置

请求参数体封装

  入口在ReflectiveFeign的newInstance方法。
在这里插入图片描述

ParseHandlersByName

  ParseHandlersByName是Feign.build的build()方法的时候构建的。
Feign.build.build()
  openFeign中的contract默认是SpringmvcContract,支持mvc注解的解析。
  ParseHandlersByName.apply方法。调用contract的parseAndValidateMetadata方法。解析获取到Feign接口的methodMetaData集合。然后遍历方法的源数据,构造Map对象,key:feign的接口名 + "#" + 方法名 + "(" + parameterTypeName集合 + ")"
value:SynchronousMethodHandler对象。对象里面包含MethodMetadata对象。
   我们知道,Feign接口代理的执行,最终会调用SynchronousMethodHandler.invoke方法。SynchronousMethodHandler对象中保存了client对象和方法的源信息。就可以进行业务的调用。

ParseHandlersByName.apply

SpringMvcFeignContract

  SpringMvcFeignContract继承SpringMvcContract。遍历Feign接口中的Method,调用Contract的parseAndValidateMetadata方法,解析封装为MethodMetadata。遍历MethodMetadata,对template进行处理。最下面,将 buildTemplate传给SynchronousMethodHandler对象。在这里插入图片描述

  方法执行时,调用SynchronousMethodHandler的invoke方法,invoke方法中调用buildTemplate的create方法,根据入参构建RequestTemplate对象。
在这里插入图片描述

  调用executeAndDecodej进行请求调用和编码处理。
  (1)获取RequestInteceptor,对template进行请求前拦截。
  (2)通过调用client.execute(request, options),发出请求。
在这里插入图片描述

路由,响应处理待完善

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

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

相关文章

LTC6268-10 4GHz 超低偏置电流 FET 输入运算放大器

LTC6268-10是一个运算放大器 具有低输入偏置电流和低输入电容。它还具有低输入参考电流噪声和电压噪声。这使得它成为高速跨组放大器和高阻抗传感器放大电路的理想选择。其为去补偿型运放&#xff0c;具有数值为 10 的稳定增益。 该器件工作在 3.1V 至 5.25V 电源&#xff0c;每…

Android View的坐标获取详解以及动画坐标分析

1、介绍 在平面图形中&#xff0c;图形都是由点坐标确定的。同样&#xff0c;Android的设备屏幕也是一个&#xff0c;如果非3D图形&#xff0c;正常的view就是一个二维坐标 参考View屏幕的左上角的坐标A(0,0),如果屏幕的宽为:width,高&#xff1a;height 那么右下角的坐标是D(…

【C/C++】使用C++和Direct3D (d3d)获取屏幕截图并根据传入分辨率进行缩放图片大小

目录 一&#xff0c;函数清单 1.Direct3DCreate9 函数 2.IDirect3D9::CreateDevice 方法 3.IDirect3DDevice9::GetDisplayMode 方法 4.IDirect3DDevice9::CreateOffscreenPlainSurface 方法 5.IDirect3DDevice9::GetFrontBufferData 方法 6.IDirect3DDevice9::D3DXLoadS…

Zookeeper工作原理

一 Zookeeper是什么 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;它是集群的管理者&#xff0c;监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终&#xff0c;将简单易…

【Spring】我抄袭了Spring,手写一套MySpring框架。。。

这篇博客实现了一个简单版本的Spring&#xff0c;主要包括Spring的Ioc和Aop功能 文章目录这篇博客实现了一个简单版本的Spring&#xff0c;主要包括Spring的Ioc和Aop功能&#x1f680;ComponentScan注解✈️Component注解&#x1f681;在spring中ioc容器的类是ApplicationConte…

迅为4412开发板Qt 界面切换

本节通过实验介绍通过创建窗口对象的方式实现界面切换&#xff1a; 步骤一&#xff1a; 在主界面 ui 文件添加 pushButton 按钮&#xff0c; 然后新建一个窗口&#xff0c;工程下创建新的 Qt 设计师界面类&#xff0c;如图 &#xff1a; 我们选择 Widget&#xff0c;用户可以根…

logstash+elasticsearch+Kibana(ELK)日志收集

文章目录一.安装elasticsearch二. 安装kibana三.配置logstash四.springboot整合logstash五.spring整合Elastic Search六. 定时清理不要一股脑执行以下语句,请观察修改要修改的地方 注意给logstash,elasticsearch,kibana释放端口,云服务器提供商和系统的端口 一.安装elasticsea…

为什么许多人吐槽C++11,那些语法值得我们学习呢?

致前行的人&#xff1a; 人生像攀登一座山&#xff0c;而找寻出路&#xff0c;却是一种学习的过程&#xff0c;我们应当在这过程中&#xff0c;学习稳定冷静&#xff0c;学习如何从慌乱中找到生机。 目录 1.C11简介 2.统一的列表初始化 2.1 &#xff5b;&#xff5d;初始化 …

linux-php 7.3.2安装

1.下载php wget http://am1.php.net/distributions/php-7.3.2.tar.gz 2.解压 tar -zxvf php-7.3.2.tar.gz 3.安装扩展 接下来进行参数配置&#xff0c;配置前如果没有libxml2和libxml2-devel会报错&#xff0c;所以应该更新libxml2并安装libxml2-devel&#xff0c;使用在线…

SpringBoot 项目整合 Redis 教程详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Android Init(后期还会更加完善)

代码分析基于android-12.0.0_r28 前期阶段 kernel/init/main.c&#xff1a; static int __ref kernel_init(void *unused) {// ...省略一堆代码if (execute_command) {ret run_init_process(execute_command);if (!ret)return 0;panic("Requested init %s failed (err…

八百字讲清楚——BCEWithLogitsLoss二分类损失函数

BCEWithLogitsLoss是一种用于二分类问题的损失函数&#xff0c;它将Sigmoid函数和二元交叉熵损失结合在一起。 假设我们有一个大小为NNN的二分类问题&#xff0c;其中每个样本xix_ixi​有一个二元标签yi∈0,1y_i\in {0,1}yi​∈0,1&#xff0c;并且我们希望预测每个样本的概率…

RCNN系列发展历程

1. RCNN RCNN发表于CVPR 2014。是将深度学习应用于目标检测领域的开山之作&#xff0c;凭借卷积神经网络较之传统CV方法的强大特征提取能力&#xff0c;将PASCAL VOC数据集检测率从35.1%提升到53.7%。 RCNN的算法流程如下图所示&#xff0c;其过程主要分为4步&#xff1a; 生成…

Ubuntu中隐藏Apache的版本号等信息

Ubuntu中隐藏Apache的版本号等信息 vim /etc/apache2/apache2.conf在apache主配置文件的末尾增加以下内容&#xff1a; ServerTokens Prod ServerSignature offServerTokens&#xff1a; 默认阀值为Full&#xff0c;以apache-2.0.55为例&#xff0c;阀值可以设定为以下某项&a…

【完整项目开发】Springboot+jsp宠物医院信息管理系统设计和实现--依旧很干

Springbootvue宠物医院信息管理系统设计和实现 **大家好&#xff0c;今天分享最近做的一套系统。**起因源于小伙伴的需求 文末有的获取方式&#xff0c;如需定制系统&#xff0c;需求发来&#xff0c;我为你分忧&#xff0c;搞起 一、 项目介绍 宠物医院信息管理系统是一种…

香港轻量云服务器安全性如何?

​  之前&#xff0c;对于新手外贸站长而言&#xff0c;部署网站首要青睐是香港虚拟主机。但大多人也都清楚&#xff0c;每台香港虚拟主机的资源是由一台物理机分割来的。这无疑会带来一定的安全隐患&#xff0c;比如分割出来的多台香港虚拟主机会共用传统的系统、管理面板以…

Vue3评分(Rate)

可自定义设置以下属性&#xff1a; 是否允许再次点击后清除&#xff08;allowClear&#xff09;&#xff0c;类型&#xff1a;boolean&#xff0c;默认 true 是否允许半选&#xff08;allowHalf&#xff09;&#xff0c;类型&#xff1a;boolean&#xff0c;默认 false star …

Java每日一练(20230417)

目录 1. N 皇后 &#x1f31f;&#x1f31f;&#x1f31f; 2. 搜索二维矩阵 &#x1f31f;&#x1f31f; 3. 发奖金问题 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 …

神经网络之反向传播算法(自适应矩估计算法Adam变形形式之Adamax、Nadam)

文章目录自适应矩估计算法&#xff08;Adam&#xff09;的两种变形形式1、Adamax算法原理2、Nadam算法原理3、算法实现3.1 Adamax训练过程3.2 Adamax测试过程及结果3.3 Nadam训练过程3.4 Nadam测试过程及结果4、参考源码及数据集自适应矩估计算法&#xff08;Adam&#xff09;的…

打造安全无忧软件应用的十大最佳实践

安全无忧的软件开发最佳实践实在是很有必要&#xff0c;因为安全风险无处不在。在网络攻击盛行的时代&#xff0c;它们可能影响到每个人&#xff0c;包括个人、公司和政府。因此&#xff0c;确保软件开发的安全性至关重要。 本篇文章将解释了什么是安全的软件&#xff0c;如何…