Feign和OpenFeign

news2024/11/28 17:37:56

1.Feign和OpenFeign的关系

Feign
Feign是一个声明式的Web服务客户端(Web服务客户端就是Http客户端),让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。
Feign是Spring Cloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用接口,就可以调用服务注册中心的服务。
Feign依赖引入:

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

OpenFeign
OpenFeign 组件的前身是 Netflix Feign 项目。后来 Feign 项目被贡献给了开源组织,才有了今天使用的 Spring Cloud OpenFeign 组件。
OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

请添加图片描述

2.OpenFeign组件的工作流程

OpenFeign 使用了一种动态代理技术来封装远程服务调用的过程,在上面的例子中看到的 helloWorldService 其实是一个特殊的接口,它是由 OpenFeign 组件中的 FeignClient 注解所声明的接口。

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

@FeignClient(value = "hello-world-service")
public interface HelloWorldService {   
  @PostMapping("/sayHello")   
  String hello(String guestName);
}

远程服务调用的信息被写在了 FeignClient 接口中。在上面的代码里,可以看到,服务的名称、接口类型、访问路径已经通过注解做了声明。OpenFeign 通过解析这些注解标签生成一个动态代理类,这个代理类会将接口调用转化为一个远程服务调用的 Request,并发送给目标服务。

OpenFeign的动态代理
在项目初始化阶段,OpenFeign 会生成一个代理类,对所有通过FeignClient 接口发起的远程调用进行动态代理。如图:
请添加图片描述
在项目启动阶段加载完成的是 1 ~ 3步 ,只有第 4 步(调用远程服务)是发生在项目的运行阶段。
关键步骤描述:

在项目启动阶段,OpenFeign 框架会发起一个主动的扫包流程,从指定的目录下扫描并加载所有被 @FeignClient 注解修饰的接口。
OpenFeign 会针对每一个 FeignClient 接口生成一个动态代理对象,即图中的 FeignProxyService,这个代理对象在继承关系上属于 FeignClient 注解所修饰的接口的实例。
这个动态代理对象会被添加到 Spring 上下文中,并注入到对应的服务里,也就是图中的 LocalService 服务。
LocalService 会发起底层方法调用。实际上这个方法调用会被 OpenFeign 生成的代理对象接管,由代理对象发起一个远程服务调用,并将调用的结果返回给 LocalService。

OpenFeign 是如何通过动态代理技术创建代理对象的?
OpenFeign 组件加载过程的重要阶段。
请添加图片描述
OpenFeign动态代理类的创建过程:

@SpringBootApplication
@EnableFeignClients
public class HelloWorldApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication .class, args);
    }

}

1.项目加载:
在项目的启动阶段,EnableFeignClients 注解扮演了启动开关的角色,它使用 Spring 框架的 Import 注解导入了 FeignClientsRegistrar 类,开始了 OpenFeign 组件的加载过程。
2.扫包:
FeignClientsRegistrar 负责 FeignClient 接口的加载,它会在指定的包路径下扫描所有的 FeignClients 类,并构造 FeignClientFactoryBean 对象来解析 FeignClient 接口。
3.解析 FeignClient 注解:
FeignClientFactoryBean 有两个重要的功能,一个是解析 FeignClient 接口中的请求路径和降级函数的配置信息;另一个是触发动态代理的构造过程。
4.构建动态代理对象
ReflectiveFeign 包含了 OpenFeign 动态代理的核心逻辑,它主要负责创建出 FeignClient 接口的动态代理对象。ReflectiveFeign 在这个过程中有两个重要任务:一个是解析 FeignClient 接口上各个方法级别的注解。将其中的远程接口 URL、接口类型(GET、POST 等)、各个请求参数等封装成元数据,并为每一个方法生成一个对应的 MethodHandler 类作为方法级别的代理;另一个重要任务是将这些 MethodHandler 方法代理做进一步封装,通过 Java 标准的动态代理协议,构建一个实现了 InvocationHandler 接口的动态代理对象,并将这个动态代理对象绑定到 FeignClient 接口上。这样一来,所有发生在 FeignClient 接口上的调用,最终都会由它背后的动态代理对象来承接。
MethodHandler 的构建过程涉及到了复杂的元数据解析,OpenFeign 组件将 FeignClient 接口上的各种注解封装成元数据,并利用这些元数据把一个方法调用“翻译”成一个远程调用的 Request 请求。
元数据的解析是依赖于 OpenFeign 组件中的 Contract 协议解析功能。Contract 是 OpenFeign 组件中定义的顶层抽象接口,它有一系列的具体实现,其中 SpringMvcContract 这个类名字中就能看出来,它是专门用来解析 Spring MVC 标签的。
SpringMvcContract 的继承结构是 :SpringMvcContract->BaseContract->Contract。
这里拿一段 SpringMvcContract 的代码块,理解它是如何将注解解析为元数据的。这段代码的主要功能是解析 FeignClient 方法级别上定义的 Spring MVC 注解。

// 解析FeignClient接口方法级别上的RequestMapping注解
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
   
   // 如果方法上没有使用RequestMapping注解,则不进行解析
   // 其实GetMapping、PostMapping等注解都属于RequestMapping注解
   if (!RequestMapping.class.isInstance(methodAnnotation)
         && !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
      return;
   }

   // 获取RequestMapping注解实例
   RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
   // 解析Http Method定义,即注解中的GET、POST、PUT、DELETE方法类型
   RequestMethod[] methods = methodMapping.method();
   // 如果没有定义methods属性则默认当前方法是个GET方法
   if (methods.length == 0) {
      methods = new RequestMethod[] { RequestMethod.GET };
   }
   checkOne(method, methods, "method");
   data.template().method(Request.HttpMethod.valueOf(methods[0].name()));

   // 解析Path属性,即方法上写明的请求路径
   checkAtMostOne(method, methodMapping.value(), "value");
   if (methodMapping.value().length > 0) {
      String pathValue = emptyToNull(methodMapping.value()[0]);
      if (pathValue != null) {
         pathValue = resolve(pathValue);
         // 如果path没有以斜杠开头,则补上/
         if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
            pathValue = "/" + pathValue;
         }
         data.template().uri(pathValue, true);
         if (data.template().decodeSlash() != decodeSlash) {
            data.template().decodeSlash(decodeSlash);
         }
      }
   }

   // 解析RequestMapping中定义的produces属性
   parseProduces(data, method, methodMapping);

   // 解析RequestMapping中定义的consumer属性
   parseConsumes(data, method, methodMapping);

   // 解析RequestMapping中定义的headers属性
   parseHeaders(data, method, methodMapping);
   data.indexToExpander(new LinkedHashMap<>());
}

通过上面的方法可以看到,OpenFeign 对 RequestMappings 注解的各个属性都做了解析。
如果项目中使用的是 GetMapping、PostMapping 之类的注解,没有使用 RequestMapping,那么 OpenFeign 也可以解析。以 GetMapping 为例,它对 RequestMapping 注解做了一层封装。如下代码片段,这个注解头上也挂了一个 RequestMapping 注解。因此 OpenFeign 可以正确识别 GetMapping 并完成加载。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
// ...省略部分代码
}

OpenFeign调用过程
OpenFeign 其实底层调用的是 Feign 的方法,生成了代理类,使用的是 JDK 的动态代理,然后 bean 注入。
调用过程,就是代理类作为客户端向被调用方发送请求,接收相应的过程。其中,feign 自行封装了 JDK java.net 相关的网络请求方法,请求过程中还有 Loadbalancer 进行负载均衡;收到响应后,还会对响应类进行解析,取出正确的响应信息。

3.OpenFeign的使用

1.创建服务
2.pom引入
3.改yml
4.主启动类

@EnableFeignClients     //激活对Feign的使用
@SpringBootApplication
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class, args);
    }
}

5.业务代码
5.1 编写服务调用者,FeignClient的使用

@Component
@FeignClient(value = "cloud-payment-service")   //需要寻找的微服务名称
public interface PaymentFeignService {
 
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}
5.2 对应的的服务提供者,普通的的SpringMVC
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id) {
        Payment payment = paymentService.getPaymentById(id);
        log.info("插入结果:" + payment);
 
        if(payment != null) {
            return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment);
        } else {
            return new CommonResult(444, "没有对应记录,查询ID:" + id);
        }
    }
5.3 服务调用者,编写controller层调用服务提供者service接口
@RestController
@Slf4j
public class OrderFeignController {
    @Resource
    private PaymentFeignService paymentFeignService;
 
    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }
}

6.超时控制
Feign客户端调用远程接口时,默认会等待1秒钟,如果服务端处理,超过1秒钟则会报错,可以在yml文件中配置超时时间

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 表示建立连接后从服务器读取到可用资源,所用时间
 ReadTimeout:  5000
# 表示建立连接时间,适用于网络正常的情况下,两端连接所用时间
 ConnectTimeout: 5000

7.OpenFeign 日志打印
对 Feign 接口的调用情况进行监控和输出,通过配置,调整日志级别,从而了解 Feign 中 Http 请求的细节。
日志级别:

  • NONE:默认的,不显示任何日志
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间
  • HEADERS:除了 BASIC 中定义的信息外,还有请求和响应的头信息
  • FULL:除了 HEADERS 中定义的信息外,还有请求和响应的正文及元数据
    配置:
    请添加图片描述
  1. 配置config
@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}
  1. YML文件里需要开启日志的Feign客户端
logging:
 level:
  #feign日志以什么级别监控哪个接口
  com.uclass.springcloud.service.PaymentFeignService: debug

请添加图片描述
8.拦截器
实现RequestInterceptor接口,只要通过FeignClient访问的接口都会走这个地方,所以使用的时候要注意一下。

@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate temp) {
        temp.header(HttpHeaders.AUTHORIZATION, "XXXXX");
    }
}

4.FeignClient注解

public @interface FeignClient {
    @AliasFor("name")
    String value() default "";
 
    String contextId() default "";
 
    @AliasFor("value")
    String name() default "";
 
    String qualifier() default "";
 
    String url() default "";
 
    boolean decode404() default false;
 
    Class<?>[] configuration() default {};
 
    Class<?> fallback() default void.class;
 
    Class<?> fallbackFactory() default void.class;
 
    String path() default "";
 
    boolean primary() default true;
}

各个属性进行分析:

value、name
value和name的作用一样,如果没有配置url那么配置的值将作为服务名称,用于服务发现。反之只是一个名称。value和name必须配一个

contextId
我们不想将所有的调用接口都定义在一个类中,有一种解决方案就是为每个Client手动指定不同的contextId,这样就不会冲突了。

url
url用于配置指定服务的地址,相当于直接请求这个服务,不经过Ribbon的服务选择。像调试等场景可以使用。

decode404
当调用请求发生404错误时,decode404的值为true,那么会执行decoder解码,否则抛出异常。

configuration
configuration是配置Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等。

fallback
定义容错的处理类,也就是回退逻辑,fallback的类必须实现Feign Client的接口,无法知道熔断的异常信息。(不推荐)

fallbackFactory
也是容错的处理,可以知道熔断的异常信息。(推荐)注意!客户端要熔断降级必须在yaml文件里进行配置,客户端:

feign:
  hystrix:
    enabled:true

path
path定义当前FeignClient访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。

primary
primary对应的是@Primary注解,默认为true,官方这样设置也是有原因的。当我们的Feign实现了fallback后,也就意味着Feign Client有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,所以我们需要设置一个优先级高的,@Primary注解就是干这件事情的。

qualifier
qualifier对应的是@Qualifier注解,使用场景跟上面的primary关系很淡,一般场景直接@Autowired直接注入就可以了。

5.参考链接

@FeignClient value/name 值重复导致报错的解决办法
链接1🔗
链接2🔗
链接3🔗
链接4🔗

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

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

相关文章

MySQL基础学习---7、子查询

子查询 子查询指的是一个查询语句嵌套在另一个查询语句内部的查询&#xff08;从MySQL4.1开始引入&#xff09;。1、子查询的基本使用 语法格式&#xff1a;select select_listfrom tablewhere expr operator(select select_listfrom table); 说明&#xff1a;1、子查询&…

【终极解决方案】IDEA maven 项目修改代码不生效。

【终极解决方案】IDEA maven 项目修改代码不生效。 文章目录 【终极解决方案】IDEA maven 项目修改代码不生效。1、项目问题描述2、可能的解决方案3、分析原因4、解决方案5、参考文献 1、项目问题描述 遇到一个非常奇怪的问题&#xff0c;修改了一个基于maven搭建的SSM项目&am…

learn_C_deep_9 (汇编角度理解return的含义、const 的各种应用场景、volatile 的基本理解与实验证明)

目录 return 关键字 const 关键字 const 修饰的只读变量 - - - 不可直接被修改&#xff01; const修饰的变量&#xff0c;可以作为数组定义的一部分吗&#xff1f; const只能在定义的时候直接初始化&#xff0c;不能二次赋值。为什么&#xff1f; const修饰指针 volatil…

opencv remap 像素重映射

remap()函数的输入是一个源图像和一个映射矩阵。映射矩阵包含了每个像素的新坐标&#xff0c;用于指定每个像素在输出图像中的位置。 假设原始图像中的一个像素的坐标为 ( x , y ) (x,y) (x,y)&#xff0c;它在输出图像中的新坐标为 ( x ′ , y ′ ) (x,y) (x′,y′)。为了计算…

Linux Python Openpyxl xlsx转html

目录 Excel转Html 示例 函数 Openpyxl知识点 其他Excel解析库问题 Excel转Html 示例 函数 import openpyxlfrom openpyxl.styles import Border from openpyxl.styles import Font from openpyxl.styles import Side from openpyxl.styles import Alignment from openpy…

ChipScope 使用问题和解决方案

背景介绍 我最近在学习FPGA开发技术&#xff0c;用杜勇老师的《Xinlinx FPGA数字信号处理设计》一书&#xff0c;按照书中的例子&#xff0c;对 CXD301 开发板进行ADC、DAC示例的调试&#xff0c;使用 ChipScope 软件进行在线逻辑分析。遇到了下面的问题&#xff0c;并给出了解…

【软考数据库】第十章 系统开发与运行

目录 10.1 系统实施 10.1.1 信息系统生命周期 10.1.2 能力成熟度模型 10.1.3 软件过程开发模型 10.1.4 信息系统开发方法 10.1.5 系统分析与设计 10.1.6 结构化开发 10.2 系统测试 10.2.1 测试原则和方法 10.2.2 测试阶段 10.2.3 测试用例设计 10.2.4 调试 10.2.…

现代化个人博客系统 ModStartBlog v7.3.0 首页热门博客,UI优化调整

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议。 功能特性 丰富的模块市场&#xff0c;后台一键快速安装 …

GETOPT函数详解

一、目的 相信第一次接触linux的小伙伴在使用命令行时肯定有这样的疑惑&#xff0c;命令行各种各样的选项和选项参数是怎样实现的&#xff08;各个命令的选项的含义可以通过man手册查看&#xff09;。 二、介绍 在正式介绍之前&#xff0c;我们先来看一下命令行选项的使用&…

数据库原理+openGauss

文章目录 0. 准备工作0.1 实验环境0.2 主要内容0.3 学习资源 1. 安装部署实验&#xff08;实验一&#xff09;1.1 下载VirtualBox1.2 安装VirtualBox1.3 镜像文件导入1.4 启动虚拟机1.5 数据库使用1.6 数据库基本操作 2 表&模式&#xff08;实验二&#xff09;2.1 创建模式…

jmeter如何测试一个post请求(发送json请求报文)

目录 1.配置测试计划1.1.创建POST的HTTP请求取样器&#xff08;模拟POST请求&#xff09;1.2.创建HTTP信息头管理器 2.执行压测并查看结果 jmeter如何测试一个发送json报文的post请求. 更详细的配置请参考另外一篇博文&#xff1a; jmeter如何测试一个get请求 1.配置测试计划 …

独立站运营必做的6项工作内容

独立站的运营工作内容比较繁琐和全面&#xff0c;主要包括以下几个方面&#xff1a; 一、网站策划和设计 定义网站目标和定位制定网站规划和设计方案确定网站主题、颜色和页面布局确定网站的核心功能和用户体验设计网站的Logo和其他品牌元素 二、网站建设和维护 选择网站主…

React基础入门【一】

官方文档&#xff1a;https://react.docschina.org/ 说明 本文总结自尚硅谷课程。学习本教程之前&#xff0c;最好具备vue的基础知识&#xff0c;明白虚拟DOM、jsx这些前置知识。接下来&#xff0c;我们通过一个简单的示例来展示react的使用。 注意&#xff1a;入门的学习不…

【Atlas 200】华为昇腾Atlas 200加速模块RC场景无法启动卡在Start to jump Linux kernel

问题现象 RC场景下&#xff0c;装有华为昇腾Atlas 200加速模块的开发板无法启动系统。 将制作好系统的SD卡插入开发板&#xff0c;0号串口输出的日志卡在Start to jump Linux kernel&#xff0c;之后没有更多输出。 可能原因 一种比较大的可能是——在制作系统时&#xff0c…

Mac终端代理

1.打开代理查看代理端口号 打开设置&#xff0c;点击网络&#xff0c;点击详细信息&#xff0c;点击代理查看代理端口号。 2.修改环境变量 1&#xff09;终端输入下面命令 vim .zshrc 2&#xff09;在.zshrc文件里添加下面两段内容&#xff08;注意&#xff1a;7980为端口号…

力扣206反转链表:代码实现+图文全解+方法总结(四种方法)

文章目录 第一部分&#xff1a;题目描述第二部分&#xff1a;题解2.1 方法一&#xff1a;生成新节点到新链表2.2 方法二&#xff1a;复用旧节点到新链表&#x1f340; 面向过程式思想方法&#x1f340; 面向对象式思想方法 2.3 方法三&#xff1a;递归2.4 旧链表中移动旧节点 第…

02:MYSQL---DML

目录 1:介绍 2:DML数据操作 1:介绍 DML英文全称是Data Manipulation Lanquage(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增删改操作。 添加数据 :insert 修改数据:update 删除数据:delete 2:DML数据操作 给指定字段添加数据 insert into 表名(字段名1,…

segment-anything本地部署使用

前言 Segment Anything Model&#xff08;SAM&#xff09;是一种先进的图像分割模型&#xff0c;它基于Facebook AI在2020年发布的Foundation Model3&#xff0c;能够根据简单的输入提示&#xff08;如点或框&#xff09;准确地分割图像中的任何对象&#xff0c;并且无需额外训…

将项目导入到github全过程

新建仓库 完善仓库信息 然后点击创建仓库 复制仓库地址 将文件上传到git上 我这里要上传IMProject文件夹&#xff0c;所以就在这个文件夹内部&#xff0c;右键鼠标&#xff0c;然后点击git bash here 输入git init &#xff0c;然后文件夹里面就会多一个.git文件 输入gi…

【IoT】ChatGPT 与 AI 硬件

随着AI的发展&#xff0c;比如最近炒得很火的ChatGPT&#xff0c;还在持续快速迭代更新。 当然了&#xff0c;对于软件和算法&#xff0c;如果你想&#xff0c;每天迭代 10 个版本都可以。 包括科大讯飞的星火认知大模型最近也刚发布。 这就引出了未来一个更大的发展方向&am…