探秘 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 定义注册到 Spring 容器中,在注册过程中,它会进行一系列的检查和处理,比如验证 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 是如何自动扫描、完成注册操作。
这种自动化的机制,大大简化了微服务之间的通信,使开发者能够专注于业务逻辑,而无需关心底层的调用细节。

在下一篇文章中,我们将探讨已经注册的bean是如何生成以及什么时机生成客户端代理的,敬请期待!

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

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

相关文章

【论文阅读】Reliable, Adaptable, and Attributable Language Models with Retrieval

文章目录 OverviewCurrent Retrieval-Augmented LMsArchitectureTraining Limitations & Future Work Overview Parametic language models的缺点&#xff1a; 事实性错误的普遍存在验证的难度&#xff08;可溯源性差&#xff09;难以在有顾虑的情况下排除某些序列适应调整…

web pdf 图片拖动图片合成

web pdf 图片拖动图片合成 先看效果 前端 合成后 1.原理 以前写过相关的帖子&#xff0c;使用的是 canva 但是这次换了一个思路使用的是图片 1.先把pdf转成图片 2.把pdf图片和目标图片传到浏览器 3.原理就和图片合成一样了。见上一篇帖子 4.后端也一样只不过这次是将位置和pd…

LeetCode 107.二叉树的层次遍历 II

题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历&#xff09; 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[1…

【力扣专题栏】两数相加,如何实现存储在链表中的整数相加?

题解目录 1、题目描述解释2、算法原理解析3、代码编写&#xff08;原始版本&#xff09;4、代码编写&#xff08;优化版本&#xff09; 1、题目描述解释 2、算法原理解析 3、代码编写&#xff08;原始版本&#xff09; /*** Definition for singly-linked list.* struct ListN…

快速上手机器学习-朴素贝叶斯

朴素贝叶斯 引言&#xff1a;本文通过介绍先验概率&#xff0c;后验概率&#xff0c;条件概率计算和贝叶斯定理等概率论内容引入朴素贝叶斯分类算法的基本思路&#xff0c;朴素贝叶斯的最终分类思想是将输入分类给概率最大的类&#xff0c;这也是概率模型算法的共有思想。本文专…

JVM(HotSpot):GC之G1垃圾回收器

文章目录 一、简介二、工作原理三、Young Collection 跨代引用四、大对象问题 一、简介 1、适用场景 同时注重吞吐量&#xff08;Throughput&#xff09;和低延迟&#xff08;Low latency&#xff09;&#xff0c;默认的暂停目标是 200 ms超大堆内存&#xff0c;会将堆划分为…

第12次CCF CSP认证真题解

1、最小差值 题目链接&#xff1a;https://sim.csp.thusaac.com/contest/12/problem/0 100分代码&#xff1a; #include <iostream> #include <algorithm> using namespace std; int main(int argc, char *argv[]) {int n;cin >> n;int a[1010],b[1010];f…

【读书笔记·VLSI电路设计方法解密】问题28:什么是芯片可靠性

一块VLSI芯片不仅需要在前几天、几周或几个月内正常工作,还必须在整个使用寿命内可靠运行。任何用于商业目的的芯片的寿命通常定义为100,000小时或约11.4年。然而,在IC的设计、制造、组装和测试过程中,许多因素可能导致其早期失效。这一芯片开发方面被称为芯片可靠性。测试相…

LinkedList和链表之刷题课(下)

1. 给定x根据x把链表分割,大的结点放在x后面,小的结点放在x前面 题目解析: 注意此时的pHead就是head(头节点的意思) 基本上就是给定一个链表,我们根据x的值来把这个链表分成俩部分,大的那部分放在x后面,小的那部分放在x前面,并且我们不能改变链表本来的顺序,比如下面的链表,我…

【数据结构与算法】《Java 算法宝典:探秘从排序到回溯的奇妙世界》

目录 标题&#xff1a;《Java 算法宝典&#xff1a;探秘从排序到回溯的奇妙世界》一、排序算法1、冒泡排序2、选择排序3、插入排序4、快速排序5、归并排序 二、查找算法1、线性查找2、二分查找 三、递归算法四、动态规划五、图算法1. 深度优先搜索&#xff08;DFS&#xff09;2…

Ubuntu22.04环境搭建MQTT服务器

官网&#xff1a; https://mosquitto.org 1.引入库 sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa2.升级安装工具 sudo apt-get update 3.安装 sudo apt-get install mosquitto 4.安装客户端 sudo apt-get install mosquitto-clients5.添加修改配置文件 进…

MySql数据库中数据类型

本篇将介绍在 MySql 中的所有数据类型&#xff0c;其中主要分为四类&#xff1a;数值类型、文本和二进制类型、时间日期、String 类型。如下&#xff08;图片来源&#xff1a;MySQL数据库&#xff09;&#xff1a; 目录如下&#xff1a; 目录 数值类型 1. 整数类型 2. …

Python | Leetcode Python题解之第516题最长回文子序列

题目&#xff1a; 题解&#xff1a; class Solution:def longestPalindromeSubseq(self, s: str) -> int:n len(s)dp [[0] * n for _ in range(n)]for i in range(n - 1, -1, -1):dp[i][i] 1for j in range(i 1, n):if s[i] s[j]:dp[i][j] dp[i 1][j - 1] 2else:dp…

【java】java的基本程序设计结构04-数值类型的转换

类型默认值 int, short, long, byte 的默认值是0。char 的默认值是 \u0000&#xff08;空字符&#xff09;。float 的默认值是 0.0f。double 的默认值是 0.0d。boolean 的默认值是 false。引用类型&#xff08;类、接口、数组&#xff09;的默认值是 null。 引用类型 在Java中…

Kafka如何控制消费的位置?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka如何控制消费的位置?】面试题&#xff1f;希望对大家有帮助&#xff1b; Kafka如何控制消费的位置? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Kafka 中&#xff0c;控制消费位置主要通过以下几个机制来实…

C++-继承

目录 一、继承的概念和定义 1、继承概念 2、继承的语法格式 3、继承的方式 4、继承类模板 二、基类和派生类之间的转换 三、继承中的作用域 四、派生类的默认成员函数 一、默认成员函数介绍 1、派生类中基类成员的构造 2、派生类中基类成员拷贝构造 3、复制重载 4、…

帝佛卡干邑荣耀登陆泰国王权King Power

帝佛卡干邑与泰国王权免税集团&#xff08;King Power&#xff09;达成深度合作&#xff0c;共同将法国帝佛卡干邑品牌推向泰国旅游零售市场。此次合作不仅标志着帝佛卡干邑在国际市场的又一重要突破&#xff0c;也彰显了双方对高品质产品的共同追求。 帝佛卡干邑于2024年8月正…

[Python学习日记-53] Python 中的正则表达式模块 —— re

[Python学习日记-53] Python 中的正则表达式模块 —— re 简介 re 模块 练习 简介 我们在编程的时候经常会遇到想在一段文字当中找出电话号码、身份证号、身高、年龄之类的信息&#xff0c;就像下面的数据一样 # 文件名&#xff1a;美丽学姐联系方式.txt 姓名 地区 …

WinRAR技巧:如何独立压缩文件夹内的每个文件?

不知道大家是否会遇到这种情况&#xff0c;将文件夹内的多个文件或文件夹压缩成一个个压缩包文件&#xff0c;这种情况除了将文件夹中的文件一个个压缩&#xff0c;还有什么批量操作的方法呢&#xff1f;今天分享使用WinRAR批量压缩文件到每个单独的文件夹的方法。 方法如下&a…

pdf压缩如何操作?教你8招,轻松搞定文件压缩!

电脑上如果存有大量的pdf文件&#xff0c;那么一定会占用一定的空间&#xff0c;不仅不利于存储还影响传输速度。如果想要将pdf压缩变小&#xff0c;那么本文分享的内容可就要了解下了。 PDF文件怎么压缩变小&#xff1f;pdf压缩是一种常见的文件处理需求。无论是处理个人文档…