SpringCloud之@FeignClient()注解的使用方式

news2024/11/19 16:48:50

@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服务调用的工作原理可以总结为以下几个步骤

  1. 首先通过@EnableFeignCleints注解开启FeignCleint。
  2. 根据Feign的规则实现接口,添加@FeignCleint注解。程序启动后,会扫描所有有@FeignCleint的类,并将这些信息注入到ioc容器中。
  3. 注入时从FeignClientFactoryBean.class获取FeignClient。
  4. 当接口的方法被调用时,通过jdk的代理,来生成具体的RequesTemplate,RequesTemplate生成http的Request。
  5. Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp。
  6. 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 null
    beanDefinition.setPrimary(primary);

    // 配置了qualifier优先用qualifier
    String 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 {
	@Bean
	public Logger.Level getLoggerLevel() {
		return Logger.Level.FULL;
	}
	@Bean
	public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
		return new BasicAuthRequestInterceptor("user", "password");
	}
	
	@Bean
	public 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 {
	@Override
	public 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);
	
	@Override
	public OrderRemoteClient create(Throwable cause) {
		return new OrderRemoteClient() {
			@Override
			public 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 {

    @Override
    public 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;
}

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

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

相关文章

Vim 文本编辑工具

Vim 基础命令 一、Vim 命令速查 Vim 是一款功能强大的文本编辑器&#xff0c;广泛应用于Linux系统中。以下是一些基础但非常有用的Vim命令&#xff0c;它们将帮助你更高效地使用Vim。 使用单个字母键通常需要进一步的输入以形成完整命令。特殊符号用来表示操作的位置。 命令…

Linux网络:传输层TCP协议(四)拥塞控制及延迟应答

目录 一、拥塞控制 二、延迟应答 一、拥塞控制 虽然 TCP 拥有滑动窗口这个大杀器机制来根据具体情况对发送的数据大小和速度进行实时控制, 能够高效并且可靠的发送大量的数据. 但是如果在双方建立好连接后的刚开始阶段就发送大量的数据。仍然可能引发一些问题. 因为同一个网…

【2024蓝桥杯/C++/A组/团建】

题目 代码 #include<bits/stdc.h> using namespace std;const int N 2e510;int a[N], b[N]; int ans; vector<int> Ga[N], Gb[N];void dfs(int ap, int af, int bp, int bf, int dep) {ans max(ans, dep);map<int, int> bk;for(auto ason : Ga[ap])if(aso…

免费【2024】springboot 程序设计基础视频学习系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

CSS:mix-blend-mode属性(设置元素的混合模式)

目录 一、mix-blend-mode属性介绍 二、mix-blend-mode常用属性值 三、mix-blend-mode属性应用 四、文字智能适配背景 1、原始样式 2、添加混合 3、实现代码 一、mix-blend-mode属性介绍 CSS中的【mix-blend-mode属性】描述了元素的内容应该与元素的直系父元素的内容和…

nodejs安装及环境配置轨道交通运维检测系统App-OA人事办公排班故障维修

✌网站介绍&#xff1a;✌10年项目辅导经验、专注于计算机技术领域学生项目实战辅导。 ✌服务范围&#xff1a;Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app、大数据等设计与开发。 ✌服务内容&#xff1a;免费功能设计、免费提供开题答辩P…

【前端 09】JavaScript中的对象与JSON

JavaScript中的对象与JSON 在JavaScript中&#xff0c;对象和JSON&#xff08;JavaScript Object Notation&#xff09;是两个紧密相连但又有区别的概念。它们都在数据处理和交换中扮演着重要角色。本文将详细讲解JavaScript中的自定义对象以及JSON对象的基本概念、格式、用法…

赵本山:我跟你找游大队去,王平:实话告诉你,我就是游队长——小品《卖梨》(下)的台词与解说

赵本山&#xff1a;我跟你找游大队去&#xff0c;王平&#xff1a;实话告诉你&#xff0c;我就是游队长 ——小品《卖梨》&#xff08;下&#xff09;的台词与解说 &#xff08;接上&#xff09; 王平&#xff08;饰演警察&#xff09;&#xff1a;你少废话 你赶紧给我挪地方…

视觉SLAM第二讲

SLAM分为定位和建图两个问题。 定位问题 定位问题是通过传感器观测数据直接或间接求解位置和姿态。 通常可以分为两类&#xff1a;基于已知地图的定位和基于未知地图的定位。 基于已知地图的定位 利用预先构建的地图&#xff0c;结合传感器数据进行全局定位。SLAM中的全局…

USB 2.0 协议专栏之 USB 2.0 连接与枚举(二)

前言&#xff1a;本篇博客为手把手教学的 USB 2.0 协议栈类精品博客&#xff0c;该专栏博客侧重针对 USB 2.0 协议进行讲解。本篇博客将针对 USB 2.0 中的连接与枚举进行教学&#xff0c;USB 的枚举过程是 USB 协议中至关重要的一环&#xff0c;也是嵌入式工程师必须掌握的内容…

杂谈(杂鱼谈论c语言)——2.大小端字节序

⼤⼩端字节序和字节序判断 当我们了解了整数在内存中存储后&#xff0c;我们调试看⼀个细节&#xff1a; #include <stdio.h> int main() {int a 0x11223344;return 0; } 调试的时候&#xff0c;我们可以看到在a中的 0x11223344 这个数字是按照字节为单位&#xff0c;…

【多模态大模型】 ALBEF in NeurIPS 2021

一、引言 论文&#xff1a; Align before Fuse: Vision and Language Representation Learning with Momentum Distillation 作者&#xff1a; Salesforce Research 代码&#xff1a; ALBEF 特点&#xff1a; 该方法使用ViT进行图像特征提取&#xff0c;提出将BERT分两部分&am…

解密阿里大神写的天书般的Tree工具类,轻松搞定树结构!

首发公众号&#xff1a;赵侠客 一、引言 最近公司新进了不少新人&#xff0c;包括一些来自阿里、网易等大型企业的资深工程师。我们组的一位新同事是阿里来的专家&#xff0c;我在CR&#xff08;Code Review, 简称CR&#xff09;时看到了他编写的一个关于树操作的工具类&#…

用qt调试can通信,波特率如何设置

硬件环境介绍&#xff1a; 1、usb转can通信模块型号为创芯科技的USB-CAN适配器&#xff0c;厂家提供的测试软件和demo程序&#xff0c;如下图所示&#xff1b; 2、下位单片机STM32&#xff0c;can通信参数如下图&#xff0c;该测试程序时单片机一直在发送数据&#xff1b; 测试…

STM32F103 RT-thread配置LCD的FMC

使用的正点原子F103ZET6开发板&#xff0c;屏幕是一块4.3寸的TFTLCD&#xff0c;接下来直接讲配置流程 参考文章&#xff1a;基于正点原子F103精英板和CubeIDE的Hal库LCD驱动移植&#xff08;从零开始&#xff09;_正点原子 cubeide-CSDN博客 1&#xff0c;使用RT_Thread Stu…

最新版Bertom降噪,压缩,均衡,简单好用有效,win和mac,支持Intel和M芯片

一。Denoiser Classic 3.07 win&mac 1&#xff09; Denoiser Classic是一个零延迟降噪插件&#xff0c;用于音乐&#xff0c;后期制作和现场使用。 2&#xff09;产品特点&#xff1a; Bertom Denoiser是一个专为音乐和后期制作/对话设计的降噪插件。 一个简单的用户界面&…

深入理解计算机系统 CSAPP 家庭作业11.8

回收子进程是书本537页的内容 在tiny.c文件加以下代码,记得重新编译哦 书中提到CGI是在动态内容中的,所以题目的意思应该是在动态内容里面回收 void handler1(int sig) {int olderrno errno;while (waitpid(-1,NULL,0)>0){Sio_puts("Handler reaped child\n");…

光伏电站气象站:现代光伏系统的重要组成部分

光伏电站气象站&#xff0c;作为现代光伏系统的重要组成部分&#xff0c;集成了气象学、电子信息技术、数据处理与分析等多学科技术于一体&#xff0c;能够实时监测并记录包括温度、湿度、风速、风向、太阳辐射强度、降雨量在内的多种气象参数。这些数据不仅是评估光伏板发电效…

基于粒子群优化算法(PSO)永磁同步电机电流环多参数辨识MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 仿真模型简介 在同步旋转dq 轴坐标系下建立PMSM 数学模型&#xff0c;将定子dq 轴电压设为辨识模型和实际测量值的输入&#xff0c;设计了PSO 辨识PMSM 参数的适应度函数。该辨识方法不需推导复杂的电机数学…