探秘 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的收敛权重做初…

Python unstructured库详解:partition_pdf函数完整参数深度解析

Python unstructured库详解&#xff1a;partition_pdf函数完整参数深度解析 1. 简介2. 基础文件处理参数2.1 文件输入参数2.2 页面处理参数 3. 文档解析策略3.1 strategy参数详解3.2 策略选择建议 4. 表格处理参数4.1 表格结构推断 5. 语言处理参数5.1 语言设置 6. 图像处理参数…

CentOS9 Stream上安装Edge浏览器

CentOS9 Stream上安装Edge浏览器 1. 下载 Microsoft Edge RPM 包2. 安装 Edge 浏览器3. 启动 Microsoft Edge4. 更新 Microsoft Edge&#xff08;可选&#xff09; 如果运行的时候出现错误&#xff1a;[5809:5809:1030/234136.530802:ERROR:zygote_host_impl_linux.cc(101)] Ru…

深度了解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的安装和配置。…

20 Docker容器集群网络架构:三、Docker集群部署

文章目录 Docker容器集群网络架构:三、Docker集群离线部署3.1 环境准备3.1.1 配置主机名3.1.2 关闭firewall防火墙3.1.3 关闭iptables防火墙3.1.3.1 安装iptables3.1.3.2 禁用iptables3.1.3.3 清空防火墙规则3.1.4 关闭selinux3.1.4.1 临时关闭3.1.4.2 永久关闭3.1.4.3 查询关…

分别用webpack和vite注册全局组件

基础组件的自动化全局注册 1. 组件全部导入后&#xff0c;批量注册 import dgDialog from "/components/dgDialog/index.vue"; import svgIcon from "/components/svgIcon/index.vue"; const allComponent { dgDialog, svgIcon }; export default {inst…

二十六、Python基础语法(函数进阶-下)

一、多值参数 多值参数&#xff08;可变参数、不定长参数&#xff09;&#xff1a;有的函数不确定参数有几个&#xff0c;在一个普通的参数前面加上一个*&#xff0c;这个参数就变为不定参数。可以接收任意多个位置传参的数据&#xff0c;类型为元组。 def test(name, *args,…

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

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

计算机网络-常用网络命令和工具

目录 Ping命令 正常的执行结果 常见的失败反馈信息 语法格式 常用参数 Ipconfig命令简介 语法格式 命令参数 tracert 命令简介 工作原理 语法格式 常用参数 nbtstat命令简介 语法格式 常用参数 netstat 命令简介 语法格式 常用参数 Ping命令 ping( Packet Internet Grope…

[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开发引擎 六、部署架构 七、产…

将.py文件生成.exe文件的方法

1、安装pyinstaller&#xff08;已有忽略&#xff09;&#xff1a;pip install pyinstaller 2、进入目标文件&#xff08;需要编译的文件&#xff09;目录 3、编译&#xff1a;pyinstaller --onefile xx.py 4、文件生成后的路径&#xff1a;在目标文件目录下创建一个名为dist的…

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

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