SpringCloud之@FeignClient()注解的使用方式
@FeignClient介绍
@FeignClient 是 Spring Cloud 中用于声明一个 Feign 客户端的注解。由于SpringCloud采用分布式微服务架构,难免在各个子模块下存在模块方法互相调用的情况。比如订单服务要调用库存服务的方法,@FeignClient()注解就是为了解决这个问题的。
Feign 是一个声明式的 Web Service 客户端,它的目的是让编写 HTTP 客户端变得更简单。通过 Feign,只需要创建一个接口,并使用注解来描述请求,就可以直接执行 HTTP 请求了。
@FeignClient()注解的源码要求它必须在Interface接口上使用( FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上)
SpringBoot服务的启动类必须要有@EnableFeignClients 注解才能使@FeginClient注解生效。
@FeignClient工作原理及整体流程
Feign服务调用的工作原理可以总结为以下几个步骤
- 首先通过@EnableFeignCleints注解开启FeignCleint。
- 根据Feign的规则实现接口,添加@FeignCleint注解。程序启动后,会扫描所有有@FeignCleint的类,并将这些信息注入到ioc容器中。
- 注入时从FeignClientFactoryBean.class获取FeignClient。
- 当接口的方法被调用时,通过jdk的代理,来生成具体的RequesTemplate,RequesTemplate生成http的Request。
- Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp。
- Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
整体流程

@FeignClient常用属性
name、value
指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
这两个属性的作用是一样的,如果没有配置url,那么配置的值将作为服务的名称,用于服务的发现,反之只是一个名称。
@FeignClient(name = "order-server")
public interface OrderRemoteClient {@GetMapping("/order/detail")public Order detail(@RequestParam("orderId") String orderId);
}
注意:
- 这里写的是你要调用的那个服务的名称(
spring.application.name属性配置),而不是你自己的那个服务的名称。 - 如果同一个工程中出现两个接口使用一样的服务名称会报错。原因是Client名字注册到容器中重复了。除非指定不同的
contextId参数。Description:
The bean ‘order-server.FeignClientSpecification’, defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
两种解决方案:
- 增加配置 spring.main.allow-bean-definition-overriding=true
- 为每个FeignClient手动指定不同的contextId
contextId
比如我们有个user服务,但user服务中有很多个接口,我们不想将所有的调用接口都定义在一个类中,那就可以给不同的client指定contextId,不然就会报Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true异常。
注意:contextId不能带_等符号。
@FeignClient(name = "order-server")
public interface OrderRemoteClient {@GetMapping("/api/order/detail", contextId = "OrderRemoteClient")public Order detail(@RequestParam("orderId") String orderId);
}
上面给出了Bean名称冲突后的解决方案,下面来分析下contextId在Feign Client的作用,在注册Feign Client Configuration的时候需要一个名称,名称是通过getClientName方法获取的:
String name = getClientName(attributes);registerClientConfiguration(registry, name,
attributes.get("configuration"));
private String getClientName(Map<String, Object> client) {if (client == null) {return null;}String value = (String) client.get("contextId");if (!StringUtils.hasText(value)) {value = (String) client.get("value");}if (!StringUtils.hasText(value)) {value = (String) client.get("name");}if (!StringUtils.hasText(value)) {value = (String) client.get("serviceId");}if (StringUtils.hasText(value)) {return value;}throw new IllegalStateException("Either 'name' or 'value' must be provided in @"+ FeignClient.class.getSimpleName());}
可以看到如果配置了contextId就会用contextId,如果没有配置就会去value然后是name最后是serviceId。默认都没有配置,当出现一个服务有多个Feign Client的时候就会报错了。
其次的作用是在注册FeignClient中,contextId会作为Client 别名的一部分,如果配置了qualifier优先用qualifier作为别名。
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("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);definition.addPropertyValue("name", name);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);// 拼接别名String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be nullbeanDefinition.setPrimary(primary);// 配置了qualifier优先用qualifierString qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
url
url用于配置指定服务的地址,相当于直接请求这个服务。像调试等场景可以使用。
@FeignClient(name = "order-server", url = "http://localhost:8085")
public interface OrderRemoteClient {@GetMapping("/api/order/detail")public Order detail(@RequestParam("orderId") String orderId);
}
path
path定义当前FeignClient访问接口时的统一前缀。
比如接口地址是/order/detail, 如果你定义了前缀是order, 那么具体方法上的路径就只需要写/detail即可。
@FeignClient(name = "order-server", url = "http://localhost:8085", path = "/api/order")
public interface OrderRemoteClient {@GetMapping("/detail")public Order detail(@RequestParam("orderId") String orderId);
}
primary
primary对应的是@Primary注解,默认为true,官方这样设置也是有原因的。当我们的Feign实现了fallback后,也就意味着Feign Client有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,所以我们需要设置一个优先级高的,@Primary注解就是干这件事情的。
qualifier
qualifier对应的是@Qualifier注解,使用场景跟上面的primary关系很淡,一般场景直接@Autowired直接注入就可以了。
如果我们的Feign Client有fallback实现,默认@FeignClient注解的primary=true, 意味着我们使用@Autowired注入是没有问题的,会优先注入你的Feign Client。
如果你鬼斧神差的把primary设置成false了,直接用@Autowired注入的地方就会报错,不知道要注入哪个对象。
解决方案很明显,你可以将primary设置成true即可,如果由于某些特殊原因,你必须得去掉primary=true的设置,这种情况下我们怎么进行注入,我们可以配置一个qualifier,然后使用@Qualifier注解进行注入。
Feign Client 定义
@FeignClient(name = "order-server", path = "/api/order", qualifier="orderRemoteClient")
public interface OrderRemoteClient {@GetMapping("/detail")public Order detail(@RequestParam("orderId") String orderId);
}
Feign Client注入
@Autowired
@Qualifier("orderRemoteClient")
private OrderRemoteClient orderRemoteClient;
configuration
configuration是配置Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等。
configuration定义
public class FeignConfiguration {@Beanpublic Logger.Level getLoggerLevel() {return Logger.Level.FULL;}@Beanpublic BasicAuthRequestInterceptor basicAuthRequestInterceptor() {return new BasicAuthRequestInterceptor("user", "password");}@Beanpublic CustomRequestInterceptor customRequestInterceptor() {return new CustomRequestInterceptor();}// Contract,feignDecoder,feignEncoder.....
}
使用示列
@FeignClient(value = "order-server", configuration = FeignConfiguration.class)
public interface OrderRemoteClient {@GetMapping("/api/order/detail")public Order detail(@RequestParam("orderId") String orderId);}
fallback
定义容错的处理类,也就是回退逻辑,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口,无法知道熔断的异常信息。
fallback定义
@Component
public class OrderRemoteClientFallback implements OrderRemoteClient {@Overridepublic Order detail(String orderId) {return new Order("order-998", "默认fallback");}}
使用示列
@FeignClient(value = "order-server", fallback = OrderRemoteClientFallback.class)
public interface OrderRemoteClient {@GetMapping("/api/order/detail")public Order detail(@RequestParam("orderId") String orderId);}
fallbackFactory
也是容错的处理,可以知道熔断的异常信息。工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
fallbackFactory定义
@Component
public class OrderRemoteClientFallbackFactory implements FallbackFactory<OrderRemoteClient> {private Logger logger = LoggerFactory.getLogger(OrderRemoteClientFallbackFactory.class);@Overridepublic OrderRemoteClient create(Throwable cause) {return new OrderRemoteClient() {@Overridepublic Order detail(String id) {logger.error("OrderRemoteClient.detail 异常", cause);return new Order("order-998", "默认");}};}
}
使用示列
@FeignClient(value = "order-server", fallbackFactory = OrderRemoteClientFallbackFactory.class)
public interface OrderRemoteClient {@GetMapping("/order/detail")public Order detail(@RequestParam("orderId") String orderId);}
@FeignClient添加Header信息
在@RequestMapping中添加
@FeignClient(url = "${orderServer_domain:http://order:8082}",value = "order-server",contextId = "OrderServerClient",path = "/api/order")
public interface OrderRemoteClient {@RequestMapping(value="/detail", method = RequestMethod.POST,headers = {"Content-Type=application/json;charset=UTF-8"})Order detail(@RequestParam("orderId") String orderId);
}
使用@RequestHeader注解添加
@FeignClient(url = "${orderServer_domain:http://order:8082}",value = "order-server",contextId = "OrderServerClient",path = "/api/order")
public interface OrderRemoteClient {@RequestMapping(value="/detail", method = RequestMethod.POST)List<String> detail(@RequestHeader Map<String, String> headerMap, @RequestParam("orderId") String orderId);
}
使用@Headers注解添加
@FeignClient(url = "${orderServer_domain:http://order:8082}",value = "order-server",contextId = "OrderServerClient",path = "/api/order")
public interface OrderRemoteClient {@RequestMapping(value="/detail", method = RequestMethod.POST)@Headers({"Content-Type: application/json;charset=UTF-8"})List<String> detail(@RequestHeader Map<String, String> headerMap, @RequestParam("orderId") String orderId);
}
实现RequestInterceptor接口
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate temp) {temp.header(HttpHeaders.AUTHORIZATION, "XXXXX");}}
FeignClient 源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.cloud.openfeign;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {@AliasFor("name")String value() default "";String contextId() default "";@AliasFor("value")String name() default "";String[] qualifiers() default {};String url() default "";boolean dismiss404() default false;Class<?>[] configuration() default {};Class<?> fallback() default void.class;Class<?> fallbackFactory() default void.class;String path() default "";boolean primary() default true;
}相关文章:
SpringCloud之@FeignClient()注解的使用方式
FeignClient介绍 FeignClient 是 Spring Cloud 中用于声明一个 Feign 客户端的注解。由于SpringCloud采用分布式微服务架构,难免在各个子模块下存在模块方法互相调用的情况。比如订单服务要调用库存服务的方法,FeignClient()注解就是为了解决这个问题的…...
20.rabbitmq插件实现延迟队列
问题 前面谈到基于死信的延迟队列,存在的问题:如果第一个消息延时时间很长,而第二个消息延时时间很短,第二个消息并不会优先得到执行。 下载插件 地址:https://github.com/rabbitmq/rabbitmq-delayed-message-excha…...
TS如何处理js模块的类型?
现在很多插件都直接用ts开发了,本身包含了类型定义常见的第三方插件,都有’types/xxx’包,安装即可使用其他的,可通过declare module定义类型 比如: // someModule.js export function greet(name) {return Hello, $…...
GPS定位系统(VUE框架)
源码下载:小宅博客网 博主之前写的《GPS定位系统(MVC框架)》版本,并没有做到前后端分离,不太适合多人协作开发,这边博主分享一个基于asp.net web api vue3的GPS定位系统框架,本框架继承了MVC框…...
分布式光伏并网AM5SE-IS防孤岛保护装置介绍——安科瑞 叶西平
产品简介 功能: AM5SE-IS防孤岛保护装置主要适用于35kV、10kV及低压380V光伏发电、燃气发电等新能源并网供电系统。当发生孤岛现象时,可以快速切除并网点,使本站与电网侧快速脱离,保证整个电站和相关维护人员的生命安全。 应用…...
神奇的方法解决Navicat闪退
原因 打开Navicat操作上面的工具等就会闪退,原因竟然是屏幕划词!!! 解决方法 看别人提到有道词典的划词功能的原因 我没有安装有道词典,但我安装豆包,它也有划词翻译的功能,关闭即可...
openmv学习笔记(24电赛笔记)
感光元件 openmv采用小孔摄像模式,将图像映射到感光原件上面,来传递图片,通过图片快速的刷新行成视频,在IDE中通过对感光原件的编辑可以控制视频的效果。 重置感光元件到默认状态 import sensor #导入感光元件这个库sensor.res…...
Linux shell编程学习笔记67: tracepath命令 追踪数据包的路由信息
0 前言 网络信息是电脑网络信息安全检查中的一块重要内容,Linux和基于Linux的操作系统,提供了很多的网络命令,今天我们研究tracepath命令。 Tracepath 在大多数 Linux 发行版中都是可用的。如果在你的系统中没有预装,请根据你的…...
生鲜云订单零售系统小程序的设计
管理员账户功能包括:系统首页,个人中心,用户管理,商品分类管理,商品信息管理,订单评价管理,订单管理,系统管理 微信端账号功能包括:系统首页,商品信息&#…...
BLE自适应跳频算法详解
前言 (1)自适应跳频算法是相当的简单,小学生都能够看懂,而且网上已经有相当多的关于自适应跳频算法的介绍。既然如此,为什么我还要写这样一篇博客呢? (2)原因很简单,我发…...
[Meachines] [Easy] Beep Elastix-CMS-LFI
信息收集 IP AddressOpening Ports10.10.10.7TCP:22, 25, 80, 110, 111, 143, 443, 993, 995, 3306 $ nmap -p- 10.10.10.7 --min-rate 1000 -sC -sV Nmap scan report for 10.10.10.7 (10.10.10.7) Host is up (0.53s latency). Not shown: 65486 filtered tcp ports (no-…...
甘肃麻花:酥脆香甜的陇原美味
在甘肃的美食画卷中,甘肃麻花以其独特的魅力占据着重要的一席之地。甘肃食家巷麻花,那金黄酥脆的外形,宛如一件件精美的艺术品。每一根麻花的纹理都清晰可见,缠绕交织,散发着诱人的光泽。 制作甘肃麻花是一门传统的手艺…...
C语言刷题小记2
前言 本篇博客还是为大家分享一些C语言的OJ题目,如果你感兴趣,希望大佬一键三连。多多支持。下面进入正文部分。 题目1竞选社长 分析:本题要求我们输入一串字符,并且统计个数的多少,那么我们可以通过getchar函数来获…...
JavaScript图片轮播
代码在文章最后面(含图片URL) 实现功能 按向左按钮图片显示上一张按向右按钮图片显示下一张每隔2000毫秒显示下一张图底部三个圆点显示当前的图片的编号 实现流程 初始化图片数组 创建一个包含图片URL的数组,轮播时会通过这个数组来切换图…...
MSSQL注入前置知识
简述 Microsoft SQL server也叫SQL server / MSSQL,由微软推出的关系型数据库,默认端口1433 常见搭配C# / .net IISmssql mssql的数据库文件 数据文件(.mdf):主要的数据文件,包含数据表中的数据和对象信息…...
idea一键为实体类赋值
file -> settings -> plugins -> marketplace 把这个插件装上 找个实体,选中,altenter进入edit界面 我是选择只保留右边这种生成方法,然后选择ok 返回到那个实体,选择,altenter generate生成...
秋招突击——7/24——知识补充——JVM类加载机制
文章目录 引言类加载机制知识点复习类的生命周期1、加载2、连接——验证3、连接——准备4、连接——解析5、初始化 类加载器和类加载机制类加载器类加载机制——双亲委派模型 面试题整理1、类加载是什么2、类加载的过程是什么3、有哪些类加载器?4、双亲委派模型是什…...
如何在 Microsoft SQL Server 中增加字段-完整指南
在使用 Microsoft SQL Server (MSSQL) 进行数据库管理时,添加新字段(列)是一项常见的任务。无论你是需要存储额外的信息,还是调整数据模型以适应新的业务需求,本指南都将帮助你轻松完成这项操作。 目录 1. 使用 T-SQL 添加字段2. 使用 SQL Server Management Studio (SSMS) 添加…...
快手电商Android一面凉经(2024)
快手电商Android一面凉经(2024) 笔者作为一名双非二本毕业7年老Android, 最近面试了不少公司, 目前已告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是《快手电商Android一面凉经(2024)》。 面试职位: Android工程师 技术一面 面试形式…...
随机点名器
练习1 package lx;import java.io.*; import java.util.ArrayList; import java.util.Collections; import java.util.Random;/*需求:需求:有一个文件里面存储了班级同学的信息,每一个信息占一行。格式为:张三-男-23要求通过程序…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
