探秘 Feign 核心注解:@FeignClient 和 @EnableFeignClients 是如何打通微服务通信的 “任督二脉” 的?

前两篇文章揭秘 Feign 调用机制:微服务通信的无缝集成和微服务通信背后的秘密:Ribbon 如何选择最佳服务实例?,我们已经了解到 Feign 调用机制的一大优势 —— 在不需要指定域名的情况下,能够借助 Ribbon 精准地找到并调用微服务。本文我们继续来分享Feign调用机制中两个最核心的注解。

一、 @FeignClient

@FeignClient 是 Spring Cloud Feign 提供的一个注解,用于声明一个 REST 客户端接口。这个注解的主要作用是告诉 Spring 框架在启动时创建一个实现了该接口的 Feign 客户端实例,并将其注册为 Spring 管理的 Bean。以下是该注解的各个属性及其功能:

  1. name / value属性:
  • 类型: String
  • 默认值: “”
  • 描述: 服务的名称,可以带有协议前缀。必填。如,@FeignClient(name = “myService”),这里的 “myService” 就是我们要调用的目标服务的名称,Feign 会依据它去服务注册中心查找对应的服务实例信息。
  1. contextId属性:
  • 类型: String
  • 默认值: “”
  • 描述: 用作 Bean 名称,但不会作为服务ID使用,如@FeignClient(contextId = “myContextId”),“myContextId” 就是我们给这个Bean 起的一个特定名称
  1. qualifier属性:
  • 类型: String
  • 默认值: “”
  • 描述: 用来指定 Feign 客户端的@Qualifier值。举个例子,@FeignClient(qualifier = “myQualifier”),“myQualifier” 就是我们为这个 Feign 客户端设置的@Qualifier值啦,在一些需要通过@Qualifier来区分不同 Bean 的场景下就会用到它。
  1. url属性:
  • 类型: String
  • 默认值: “”
  • 描述: 绝对URL或可解析的主机名(协议可选)。如@FeignClient(url = “http://cus.com”),这样 Feign 就会直接把请求发送到这个指定的地址啦,而不会再去服务注册中心查找服务实例信息
  1. decode404属性:
  • 类型: boolean
  • 默认值: false
  • 描述: 是否解码 404 错误而不是抛出 FeignException。如@FeignClient(decode404 = true),那么当遇到 404 错误时,Feign 就会尝试去解码这个错误,而不是像默认情况那样直接抛出异常给调用者。
  1. configuration属性:
  • 类型: Class<?>[]
  • 默认值: { }
  • 描述: 自定义配置类,可以覆盖默认的 Feign 配置,例如 Decoder, Encoder, Contract 等。如@FeignClient(configuration = MyFeignConfig.class),“MyFeignConfig” 就是我们自己写的配置类,通过它就能按照我们的需求灵活调整 Feign 客户端的行为。
  1. fallback属性:
  • 类型: Class<?>
  • 默认值: void.class
  • 描述: 指定的 Feign 客户端接口的容错类,必须实现该接口并是一个有效的 Spring Bean。如@FeignClient(fallback = MyFallback.class),当目标服务出现故障(比如不可用、超时等)时,Feign 就会调用 “MyFallback” 这个容错类中的对应方法来返回一个预设的结果,而不是直接抛出异常给调用者,这样就能保证系统的部分功能依然能够正常运行啦,大大提高了系统的容错。
  1. fallbackFactory属性:
  • 类型: Class<?>
  • 默认值: void.class
  • 描述: 指定的 Feign 客户端接口的容错工厂,必须生成实现该接口的实例,并是一个有效的 Spring Bean。如@FeignClient(fallbackFactory = MyFallbackFactory.class),当目标服务出现故障时,“MyFallbackFactory” 这个容错工厂类就会被用来创建合适的容错对象并处理请求,而且通过这个工厂类还能在容错逻辑中获取到导致服务降级的原因(比如异常信息等),以便进行更精细的处理
  1. path属性:
  • 类型: String
  • 默认值: “”
  • 描述: 所有方法级别映射的路径前缀,可以与 @RibbonClient 一起使用。@FeignClient(path = “/customer-api”),这样在后续通过这个 Feign 客户端接口调用方法时,请求路径就会自动加上这个前缀。
  1. primary属性:
  • 类型: boolean
  • 默认值: true
  • 描述: 是否将 Feign 代理标记为主 Bean。如@FeignClient(primary = false),那么这个 Feign 代理就不会被当作主 Bean 来处理,在一些需要区分不同优先级的 Bean 的场景下会用到这个属性

二、@EnableFeignClients

@EnableFeignClients 也是 Spring Cloud Feign 的一个注解,用于启用 Feign 客户端。这个注解通常放在配置类或启动类上,Spring 会扫描标注了 @FeignClient 注解的接口,并生成对应的客户端代理类,以便在应用中通过这些接口调用远程服务。

核心注册方法 位于 FeignClientsRegistrar 类中,主要通过以下几个方法完成 Feign 客户端的注册。

  1. registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法
  • 作用:注册默认的 Feign 配置,如果在 @EnableFeignClients 注解中指定了 defaultConfiguration 属性,则将其作为默认配置应用于所有 Feign 客户端。
  • 实现
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);// 键defaultConfiguration不为空的时候,会调用registerClientConfiguration方法把从defaultAttrs中获取到的默认配置信息注册到registryif (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {String name;if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();}else {name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));}}
  1. registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法
  • 作用: 扫描并注册所有标注了 @FeignClient 的接口,生成相应的 Feign 客户端代理类。
  • 实现
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 存储所有的BeanLinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();// EnableFeignClients注解的5Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// 过滤带有@FeignClient标记的接口AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);final Class<?>[] clients = attrs == null ? null: (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {// scanner.findCandidateComponent会获取到所有@FeignClient标准的接口candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}}……for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();// 过滤接口,因为@FeignClient只标准在接口上Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);// 配置绑定registerClientConfiguration(registry, name,attributes.get("configuration"));// 实际的注册方法registerFeignClient(registry, annotationMetadata, attributes);}}}
  1. registerFeignClient(BeanDefinitionRegistry registry,
    AnnotationMetadata annotationMetadata, Map<String, Object> attributes)方法
  • 作用:这个方法的主要任务就是根据传入的参数信息,将一个标注了@FeignClient注解的接口注册为一个具体的 Bean 定义,包括设置各种属性值、指定 Bean 的类型、设置自动装配模式等等,以便后续能够正确生成客户端代理类。
  • 实现
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);definition.addPropertyValue("name", name);// 依次设置章节1提到的@FeignClient的10个属性…… AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
  1. registerBeanDefinition(holder, registry) 方法
  • 作用:将 Bean和创建它的Factory绑定到一起,方便后续生成代理类),在注册过程中,它会进行一系列的检查和处理,比如验证 Bean 定义的有效性、处理已有相同名称 Bean 定义的情况、更新相关的缓存和列表等等,以确保 Bean 定义能够正确地注册和更新。
  • 实现
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");……BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);// 重复bean处理if (existingDefinition != null) {if (!this.isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}if (existingDefinition.getRole() < beanDefinition.getRole()) {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");}} else if (!beanDefinition.equals(existingDefinition)) {if (this.logger.isDebugEnabled()) {this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");}} else if (this.logger.isTraceEnabled()) {this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");}this.beanDefinitionMap.put(beanName, beanDefinition);} else {// 新增bean处理if (this.hasBeanCreationStarted()) {synchronized(this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;this.removeManualSingletonName(beanName);}} else {this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}// 缓存清理if (existingDefinition == null && !this.containsSingleton(beanName)) {if (this.isConfigurationFrozen()) {this.clearByTypeCache();}} else {this.resetBeanDefinition(beanName);}}

三、总结

@FeignClient 和 @EnableFeignClients 是 Feign 框架中至关重要的注解,通过剖析 @FeignClient @EnableFeignClients ,我们可以更好的理解 Feign 是如何自动扫描、完成注册操作。
这种自动化的机制,大大简化了微服务之间的通信,使开发者能够专注于业务逻辑,而无需关心底层的调用细节。

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

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

相关文章

比较24个结构的迭代次数

(A,B)---6*30*2---(0,1)(1,0) 让A是结构1&#xff0c;让B全是0。收敛误差为7e-4&#xff0c;收敛199次取迭代次数平均值&#xff0c;得到28080.98 做一个同样的网络(A,B)---6*30*2---(0,1)(1,0)&#xff0c;让A是结构1-24&#xff0c;B全是0&#xff0c;用结构1的收敛权重做初…

深度了解flink(七) JobManager(1) 组件启动流程分析

前言 JobManager是Flink的核心进程&#xff0c;主要负责Flink集群的启动和初始化&#xff0c;包含多个重要的组件(JboMaster&#xff0c;Dispatcher&#xff0c;WebEndpoint等)&#xff0c;本篇文章会基于源码分析JobManagr的启动流程&#xff0c;对其各个组件进行介绍&#x…

.NET内网实战:通过白名单文件反序列化漏洞绕过UAC

01阅读须知 此文所节选自小报童《.NET 内网实战攻防》专栏&#xff0c;主要内容有.NET在各个内网渗透阶段与Windows系统交互的方式和技巧&#xff0c;对内网和后渗透感兴趣的朋友们可以订阅该电子报刊&#xff0c;解锁更多的报刊内容。 02基本介绍 03原理分析 在渗透测试和红…

ELK之路第三步——日志收集筛选logstash和filebeat

logstash和filebeat&#xff08;偷懒版&#xff09; 前言logstash1.下载2.修改配置文件3.测试启动4.文件启动 filebeat1.下载2.配置3.启动 前言 上一篇&#xff0c;我们说到了可视化界面Kibana的安装&#xff0c;这一篇&#xff0c;会简单介绍logstash和filebeat的安装和配置。…

植物源UDP-糖基转移酶及其分子改造-文献精读75

植物源UDP-糖基转移酶及其分子改造 摘要 糖基化能够增加化合物的结构多样性,有效改善水溶性、药理活性和生物利用度,对植物天然产物的药物开发至关重要。UDP-糖基转移酶(UGTs)能够催化糖基从活化的核苷酸糖供体转移到受体形成糖苷键,植物中天然产物的糖基化修饰主要通过UGTs实…

[MySQL#7] CRUD(2) | 更新 | 删除 | 聚合函数 | group by

目录 3. 更新 4. 删除 截断表 日志的作用 5. (实验) 插入查询结果 6. 聚合函数 7. 分组查询 接着上篇文章[MySQL#6] 表的CRUD (1) | Create | Retrieve(查) | where继续讲解~ 3. 更新 语法&#xff1a; UPDATE table_name SET column expr [, column expr ...][WHE…

RegCM模式运行./bin/regcmMPI报错

1、报错 在运行RegCM时到截止模拟时间段时&#xff0c;不显示successfully&#xff0c;而是报错&#xff1a; MPI_ABORT was invoked on rank 0 in communicator MPI COMMUNICATOR 3 DUP FROM 0 with errorcode 1. NOTE: invoking MPI_ABORT causes Open MPI to kill all MP…

日本也有九九乘法表?你会读吗?柯桥零基础学日语到蓝天广场

日本也有“九九乘法表”&#xff1f; 九九乘法表起源于中国&#xff0c;可以追溯到春秋战国时代。 日本奈良县橿原市境内的“藤原京”遗址&#xff0c;出土了日本目前可找到最古老的“九九乘法表”木简。 根据日本奈良研究所的研究&#xff0c;其内容可能是1300多年前的官吏用…

Python(包和模块)

包 定义 包是将模块以文件夹的组织形式进行分组管理的方法&#xff0c;以便更好地组织和管理相关模块。 包是一个包含一个特殊的__init__.py文件的目录&#xff0c;这个文件可以为空&#xff0c;但必须存在&#xff0c;以标识目录为Python包。 包可以包含子包&#xff08;子…

集群聊天服务器——逻辑梳理

网络聊天服务器项目&#xff0c;该项目分为4个模块&#xff1a; 首先是网络模块&#xff1a;我使用了muduo高性能网络库&#xff0c;解耦合网络与业务之间这两部分代码&#xff0c;可以更加专注与业务的功能开发其次是服务层模块&#xff1a;我使用了基于C11的技术比如绑定器和…

前沿技术与未来发展第一节:C++与机器学习

第六章&#xff1a;前沿技术与未来发展 第一节&#xff1a;C与机器学习 1. C在机器学习中的应用场景 C在机器学习中的应用优势主要体现在高效的内存管理、强大的计算能力和接近底层硬件的灵活性等方面。以下是 C 在机器学习领域的几个主要应用场景&#xff1a; 1.1 深度学习…

项目解决方案:在弱网(低带宽、高延迟、有丢包的网络)环境下建设视频监控平台的设计方案(下)

目录 一、需求分析 1、业务需求分析 2、功能需求分析 二、建设目标 三、设计原则 四、标准规范建设 五、系统架构 1、视频接入管理系统 2、资源管理调度平台 3、视频转码解码服务器 4、媒体输出引擎 5、媒体录制引擎 6、智能联动引擎 7、API开发引擎 六、部署架构 七、产…

操作系统笔记(四)进程间通信,竞争条件与解决方案

进程间通信(IPC) 如何在进程间传递信息? 如何防止两个进程冲突&#xff1f; 如何实现进程执行的先后顺序&#xff1f; 竞争条件&#xff08;Race conditions&#xff09; 竞争条件&#xff08;Race conditions&#xff09; 多个进程访问一个共享数据&#xff0c;而数据最…

jmeter的基本使用

Jmeter基本使用 一、变量 1.用户定义变量 2.用户参数 二、函数 1.计数器${__counter(,)} 2.时间函数 3.加密函数${__digest(,,,,)} 4. 整数相加${__intSum(,,)} 5.属性函数&#xff0c;${__P(,)}、${__property(,,)}、${__setProperty(,,)} 6.V函数 三、获取响应数据…

Go语言基础教程:指针

在 Go 中&#xff0c;函数参数默认是按值传递的。若要改变变量的原始值&#xff0c;可以使用指针。本教程将通过示例代码来演示如何使用值传递和指针传递。 package mainimport "fmt"// 传值方式 - 函数内改变 ival 的值不会影响原始变量 func zeroval(ival int) {i…

2024年优秀的天气预测API

准确、可操作的天气预报对于许多组织的成功至关重要。 事实上&#xff0c;在整个行业中&#xff0c;天气条件会直接影响日常运营&#xff0c;包括航运、按需、能源和供应链&#xff08;仅举几例&#xff09;。 以公用事业为例。根据麦肯锡的数据&#xff0c;在 1.4 年的时间里…

HCIP-HarmonyOS Application Developer V1.0 笔记(二)

类Web开发范式自定义组件基本用法 自定义组件通过element引入到宿主页面。 Props自定义属性 自定义属性支持类型 String&#xff0c;Number&#xff0c;Boolean&#xff0c;Array&#xff0c;Object。 命名规范&#xff1a; 命名时禁止以on、、on:、grab:等保留关键字为开头…

天润融通突破AI客服局限,三大关键提升文本机器人问答效果

近期&#xff0c;AI客服再次登上热搜&#xff0c;引发网友集体吐槽&#xff0c;比如AI客服虽然态度客气&#xff0c;但听不懂客户诉求&#xff0c;回答问题驴唇不对马嘴&#xff0c;解决不了问题...... 更有网友将这些问题升级到&#xff0c;企业就是不想解决问题才交给AI客服…

aarch64-opencv341交叉编译,并在arm上部署helloopencv

背景 当需要在jetson xavier nx或者rk 3562等平台上开发关于视觉检测的工程时&#xff0c;由于arm板子资源不足或者不能联网等原因&#xff0c;通常在虚拟机上利用交叉编译器编译得到可执行程序&#xff0c;然后部署到arm板上。 aarch64-opencv341交叉编译 ubuntu虚拟机中先…

mysql中redolog、binlog

我们中说删库跑路&#xff0c;那么数据库删除后&#xff0c;里面的数据怎么恢复呢&#xff1f; 这里就涉及到了redolog和binlog了 一、什么是存储引擎和缓冲池 存储引擎是 MySQL 中直接与磁盘交互部分。也是存储引擎读写数据的最小单位&#xff0c;一个页里可以有一条或多条…