科普文:微服务之Spring Cloud 服务调用组件Openfeign

news2024/12/25 9:00:48

一、概述

Feign是声明式Web服务客户端,它使编写Web服务客户端更加容易。

Feign不做任何请求处理,通过处理注解相关信息生成Request,并对调用返回的数据进行解码,从而实现简化HTTP API的开发

如果要使用Feign,需要创建一个接口并对其添加Feign相关注解,另外Feign还支持可插拔编码器和解码器,致力于打造一个轻量级HTTP客户端。

1.1 Feign和Openfeign的区别

Feign最早是由Netflix公司进行维护的,后来Netflix不再对其进行维护,最终Feign由社区进行维护,更名为Openfeign

为了少打俩字,下文简称Openfeign为Feign。

并将原项目迁移至新的仓库,所以我们在Github上看到Feign的坐标如下:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>parent</artifactId>
    <version>...</version>
</dependency>

1.2 Starter Openfeign

当然了,基于SpringCloud团队对Netflix的情有独钟,你出了这么好用的轻量级HTTP客户端,我这老大哥不得支持一下,所以就有了基于Feign封装的Starter

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

Spring Cloud添加了对Spring MVC注解的支持,并支持使用Spring Web中默认使用的相同HttpMessageConverters

另外,Spring Cloud同时集成了RibbonEureka以及Spring Cloud LoadBalancer,以在使用Feign时提供负载均衡的HTTP客户端。

针对于注册中心的支持,包含但不限于Eureka,比如Consul、Nacos等注册中心均支持。

在我们SpringCloud项目开发过程中,使用的大多都是这个Starter Feign

二、案例

为了方便大家理解,这里写出对应的生产方、消费方Demo代码,以及使用的注册中心。

注册中心使用的Nacos,生产、消费方代码都比较简单。另外为了阅读体验感,文章原则是少放源码,更多的是给大家梳理核心逻辑。

2.1 生产者服务

添加Nacos服务注册发现注解以及发布出HTTP接口服务。

@EnableDiscoveryClient
@SpringBootApplication
public class NacosProduceApplication {
    public static void main(String[] args) {
        SpringApplication.run(NacosProduceApplication.class, args);
    }
	
    @RestController
    static class TestController {
        @GetMapping("/hello")
        public String hello(@RequestParam("name") String name) {
            return "hello " + name;
        }
    }
}

2.2 消费者服务

定义FeignClient消费服务接口。

@FeignClient(value = "nacos-produce")
public interface DemoFeignClient {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    String sayHello(@RequestParam("name") String name);
}

因为生产者使用Nacos,所以消费者除了开启Feign注解,同时也要开启Nacos服务注册发现。

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

    @Autowired 
    private DemoFeignClient demoFeignClient;

    @GetMapping("/test")
    public String test() {
        String result = demoFeignClient.sayHello("Test");
        return result;
    }
}

三、Feign的启动原理

我们在SpringCloud的使用过程中,如果想要启动某个组件,一般都是@EnableXXX这种方式注入,Feign也不例外,我们需要在类上标记此注解@EnableFeignClients

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

继续深入看一下注解内部都做了什么。注解内部的方法就不说明了,不加会有默认的配置,感兴趣可以跟下源码。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    //...
}

前三个注解是元注解,重点在第四个@Import上,一般使用此注解都是想要动态注册Spring Bean的。

3.1 注入@Import

通过名字也可以大致猜出来,这是Feign注册Bean使用的,使用到了Spring相关的接口,一起看下起了什么作用。

ResourceLoaderAwareEnvironmentAware为FeignClientsRegistrar中两个属性resourceLoader、environment赋值,对Spring了解的小伙伴理解问题不大。

ImportBeanDefinitionRegistrar负责动态注入IOC Bean,分别注入Feign配置类、FeignClient Bean

// 资源加载器,可以加载classpath下的所有文件
private ResourceLoader resourceLoader;
// 上下文,可通过该环境获取当前应用配置属性等
private Environment environment;

@Override
public void setEnvironment(Environment environment) {
    this.environment = environment;
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
}

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册@EnableFeignClients提供的自定义配置类中的相关Bean实例
    registerDefaultConfiguration(metadata, registry);
    // 扫描package,注册被@FeignClient修饰的接口类为IOC Bean
    registerFeignClients(metadata, registry);
}

3.2 添加全局配置

registerDefaultConfiguration方法流程如下

  1. 获取@EnableFeignClients注解上的属性以及对应Value
  2. 生成FeignClientSpecification(存储Feign中的配置类)对应的构造器BeanDefinitionBuilder
  3. FeignClientSpecification Bean名称为default. + @EnableFeignClients修饰类全限定名称 + FeignClientSpecification
  4. @EnableFeignClients defaultConfiguration默认为{},如果没有相关配置,默认使用FeignClientsConfiguration并结合name填充到FeignClientSpecification,最终注册为IOC Bean

3.3 注册FeignClient接口

将重点放在registerFeignClients上,该方法主要就是将修饰了@FeignClient的接口注册为IOC Bean

  1. 扫描@EnableFeignClients注解,如果有clients,则加载指定接口,为空则根据scanner规则扫描出修饰了@FeignClient的接口;
  2. 获取@FeignClient上对应的属性,根据configuration属性去创建接口级的FeignClientSpecification配置类IOC Bean
  3. @FeignClient的属性设置到FeignClientFactoryBean对象上,并注册IOC Bean

@FeignClient修饰的接口实际上使用了Spring的代理工厂生成代理类,所以这里会把修饰了@FeignClient接口的BeanDefinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean

也就是说,当我们定义@FeignClient修饰接口时,注册到IOC容器中Bean类型变成了FeignClientFactoryBean

Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean工厂Bean是一种特殊的Bean,对于需要获取Bean的消费者而言,它是不知道Bean是普通Bean或是工厂Bean的。工厂Bean返回的实例不是工厂Bean本身,而是会返回执行了工厂BeanFactoryBean#getObject逻辑的实例

四、Feign的工作原理

Feign的工作原理,核心点围绕在被@FeignClient修饰的接口,如何发送及接收HTTP网络请求。

上面说到@FeignClient修饰的接口最终填充到IOC容器的类型是FeignClientFactoryBean,先来看下它是什么。

4.1 FactoryBean接口特征

这里说一下FeignClientFactoryBean都有哪些特征。

  1. 它会在类初始化时执行一段逻辑,依据Spring InitializingBean接口;
  2. 如果它被别的类@Autowired进行注入,返回的不是它本身,而是FactoryBean#getObject返回的类,依据Spring FactoryBean接口;
  3. 它能够获取Spring上下文对象,依据Spring ApplicationContextAware接口。

先来看它的初始化逻辑都执行了什么

@Override
public void afterPropertiesSet() {
    Assert.hasText(contextId, "Context id must be set");
    Assert.hasText(name, "Name must be set");
}

没有特别的操作,只是使用断言工具类判断两个字段不为空。ApplicationContextAware也没什么说的,获取上下文对象赋值到对象的局部变量里,重点以及关键就是FactoryBean#getObject方法。

@Override
public Object getObject() throws Exception {
    return getTarget();
}

getTarget源码方法还是挺长的,这里采用分段的形式展示

<T> T getTarget() {
    // 从IOC容器获取FeignContext
    FeignContext context = applicationContext.getBean(FeignContext.class);
    // 通过context创建Feign构造器
    Feign.Builder builder = feign(context);
    //...
}

这里提出一个疑问?FeignContext什么时候、在哪里被注入到Spring容器里的?

看到图片小伙伴就明了了,用了SpringBoot怎么会不使用自动装配的功能呢,FeignContext就是在FeignAutoConfiguration中被成功创建。

4.2 初始化父子容器

feign方法里日志工厂、编码、解码等类均是通过get(...)方法得到。

这里涉及到Spring父子容器的概念,默认子容器Map为空,获取不到服务名对应Context则新建。

从下图中看到,注册了一个FeignClientsConfiguration类型的Bean,我们上述方法feign中的获取的编码、解码器等组件都是从此类中获取默认。

默认注册如下,FeignClientsConfiguration是由创建FeignContext调用父类Super构造方法传入的。

关于父子类容器对应关系,以及提供@FeignClient服务对应子容器的关系(每一个服务对应一个子容器实例)。

回到getInstance方法,子容器此时已加载对应Bean,直接通过getBean获取FeignLoggerFactory

如法炮制,Feign.BuilderEncoderDecoderContract都可以通过子容器获取对应Bean

configureFeign方法主要进行一些配置赋值,比如超时、重试、404配置等,就不再细说赋值代码了。

到这里有必要总结一下创建Spring代理工厂的前半场代码

  1. 注入@FeignClient服务时,其实注入的是FactoryBean#getObject返回代理工厂对象;
  2. 通过IOC容器获取FeignContext上下文;
  3. 创建Feign.Builder对象时会创建Feign服务对应的子容器;
  4. 从子容器中获取日志工厂、编码器、解码器等Bean
  5. Feign.Builder设置配置,比如超时时间、日志级别等属性,每一个服务都可以个性化设置。

4.3 动态代理生成

继续嗑,上面都是开胃菜,接下来是最最最重要的地方了,小板凳坐板正了..

因为我们在@FeignClient注解是使用name而不是url,所以会执行负载均衡策略的分支。

ClientFeign发送请求以及接收响应等都是由Client完成,该类默认Client.Default,另外支持HttpClientOkHttp等客户端。

代码中的ClientTargeter在自动装配时注册,配合上文中的父子容器理论,这两个Bean在父容器中存在。

因为我们并没有对Hystrix进行设置,所以走入此分支。

创建反射类ReflectiveFeign,然后执行创建实例类。

newInstance方法对@FeignClient修饰的接口中SpringMvc等配置进行解析转换,对接口类中的方法进行归类,生成动态代理类。

可以看出Feign创建动态代理类的方式和Mybatis Mapper处理方式是一致的,因为两者都没有实现类。

根据newInstance方法按照行为大致划分,共做了四件事:

  1. 处理@FeignCLient注解(SpringMvc注解等)封装为MethodHandler包装类;
  2. 遍历接口中所有方法,过滤Object方法,并将默认方法以及FeignClient方法分类;
  3. 创建动态代理对应的InvocationHandler并创建Proxy实例;
  4. 接口内default方法绑定动态代理类

MethodHandler将方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储。

到这里我们也就可以Feign的工作方式了。前面那么多封装铺垫,封装个性化配置等等,最终确定收尾的是创建动态代理类,也就是说在我们调用@FeignClient接口时,会被FeignInvocationHandler#invoke拦截,并在动态代理方法中执行下述逻辑:

  1. 接口注解信息封装为HTTP Request
  2. 通过Ribbon获取服务列表,并对服务列表进行负载均衡调用(服务名转换为ip+port);
  3. 请求调用后,将返回的数据封装为HTTP Response,继而转换为接口中的返回类型。

既然已经明白了调用流程,那就正儿八经的试一哈,试过才知有没有...

  • RequestTemplate:构建Request模版类。
  • Options:存放连接、超时时间等配置类。
  • Retryer:失败重试策略类。

重试这一块逻辑看了很多遍,但是怎么看,一个continue关键字放到while的最后面都有点多余...

执行远端调用逻辑中使用到了Rxjava(响应式编程),可以看到通过底层获取server后将服务名称转变为ip+port的方式。

这种响应式编程的方式在SpringCloud中很常见,Hystrix源码底层也有使用

网络调用默认使用HttpURLConnection,可以配置使用HttpClient或者OkHttp,调用远端服务后,再将返回值解析正常返回,到这里一个完成的Feign调用链就聊明白了。

五、Feign如何负载均衡

一般而言,我们生产者注册多个服务,消费者调用时需要使用负载均衡从中选取一个健康并且可用的生产者服务

因为Feign内部集成Ribbon,所以也支持此特性,一起看下它是怎么做的。

我们在Nacos上注册了两个服务,端口号80808081。在获取负载均衡器时就可以获取服务集合。

然后通过chooseServer方法选择一个健康实例返回,后面会新出一篇文章对Ribbon的负载均衡详细说明。

通过返回的Server替换URL中的服务名,最后使用网络调用服务进行远端调用。

六、总结

  1. 通过@EnableFeignCleints注解启动Feign Starter组件;
  2. Feign Starter在项目启动过程中注册全局配置,扫描包下所有的@FeignClient接口类,并进行注册IOC容器;
  3. @FeignClient接口类被注入时,通过FactoryBean#getObject返回动态代理类;
  4. 接口被调用时被动态代理类逻辑拦截,将@FeignClient请求信息通过编码器生成Request
  5. 交由Ribbon进行负载均衡,挑选出一个健康的Server实例;
  6. 继而通过Client携带Request调用远端服务返回请求响应;
  7. 通过解码器生成Response返回客户端,将信息流解析成为接口返回数据。

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

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

相关文章

项目相关内容 ----- 2实现打印 ---- 图片与字符

目录 1 . 实现图图片的打印 1&#xff09;结构体定义 2&#xff09;画点线 3&#xff09;清屏 4&#xff09;图片显示 5&#xff09;主函数部分 2 . 实现字符的打印 1) 定义BMP位图文件的头部信息以及信息头​ 2&#xff09;实现打印字符的绘制​ 3 . 打印文本内容​ 附…

你会在Vision Pro里编程吗?

你会在Vision Pro里编程吗&#xff1f; Vision Pro作为一位开发者&#xff0c;你会考虑将Vision Pro应用到编程中吗&#xff1f;你认为Vision Pro有可能改变开发者的工作模式与效率吗&#xff1f; 初见Vision Pro有点陌生&#xff0c;不太了解Vision Pro是什么。那么这里先来了…

顶点着色器片段着色器

/* * FileName : OpenGL_Tutorial.cpp * Time : 2024-08-03 10:00:00 * Author : XuMing * Email : 920972751qq.com * description : 使用OpenGL进行顶点输入和着色器编译的详细解析 */#include <glad/glad.h> #include <GLFW/glfw3.…

【论文阅读visual grounding】QRNet论文解读与关键代码实现

Shifting More Attention to Visual Backbone: Query-modulated Refinement Networks for End-to-End Visual Grounding 论文链接&#xff1a;https://arxiv.org/abs/2203.15442 代码链接&#xff1a;https://github.com/z-w-wang/QRNet Motivation 视觉定位&#xff08;visua…

JavaScript基础——JavaScript变量声明

变量是存储数据的容器&#xff0c;可以变的量&#xff0c;值可以改变&#xff0c;在JavaScript中&#xff0c;变量声明的关键字有var、let&#xff0c;其中&#xff0c;var是ES5的语法&#xff0c;let是ES6的语法&#xff0c;变量需要先声明&#xff0c;在使用。 声明一个age变…

整除分块, CF538 F - A Heap of Heaps

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 F - A Heap of Heaps 二、解题报告 1、思路分析 给定v&#xff0c;k&#xff0c;v的父节点p (v - 2) / k 1 我们令P p - 1&#xff0c;V V - 2 P V / k&#xff0c;我们发现这就是一个整除分块问题…

Ubuntu22.04之有道词典mini窗口无法拖动问题(二百六十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

混合域注意力机制(空间+通道)

在计算机视觉任务中&#xff0c;空间域注意力通常关注图像中不同位置的重要性&#xff0c;例如突出图像中的关键对象或区域。而通道域注意力则侧重于不同通道&#xff08;特征图&#xff09;的重要性&#xff0c;决定哪些特征对于任务更具判别力。混合域注意力机制结合了空间域…

FIR低通滤波器

FIR低通滤波器 FIR(Finite Impulse Response)滤波器:有限长单位冲激响应滤波器,又称为非递归型滤波器,是数字信号处理系统中最基本的元件,它可以在保证任意幅频特性的同时具有严格的线性相频特性,同时其单位抽样响应是有限长的,因而滤波器是稳定的系统。 MATLAB实现…

详细了解架构师

架构师的核心职责是消除不确定性和降低复杂性&#xff01; 架构师画像 架构师的定位 架构设计环 澄清和技术相关的&#xff0c;比如支持百万级别的&#xff0c;架构师需要澄清&#xff0c;可能只有十万级。 架构师的三个核心能力 架构师的三个关键思维 架构设计流程和架构师…

初识MQ——学习MQ之前需要了解的知识点

目录 前言 1. 同步和异步通讯 1.1 同步通讯 1.2 异步通讯 2. MQ技术对比 前言 在现在的大数据时代&#xff0c;高并发的情况越来越普遍&#xff0c;系统一个不注意&#xff0c;就可能崩溃无法访问了。这是开发最不想看到的情况&#xff0c;如果是上班还好&#xff0c;可以…

【C++】—— 类与对象(二)

【C】—— 类与对象&#xff08;二&#xff09; 1、类的默认成员函数2、构造函数2.1、初见构造2.2、深入构造2.3、初始化列表2.3.1、什么是初始化列表2.3.2、初始化列表和函数体关系2.3.3、必须使用初始化列表的情况2.3.3.1、 c o n s t const const 成员变量2.3.3.2、引用成员…

AS400==tutorial for Beginners

系统AS400 语言RPGLE 参考视频&#xff1a; https://www.youtube.com/watch?vFqgwYsp7mjk&listPL3W4xRdnQJHVWWmYX1Klji7QUk_PQhq0t&index5 Lesson 1 | Introduction to As-400 and setting up As-400 Environment. 客户端软件TN5250 Terminal Emulation for Window…

MyBatis全方位指南:从注解到XML文件的数据库操作

目录 一.什么是MyBatis 入门程序初体验 二.MyBatis基本操作CRUD ▐ 增(Insert) 返回主键 ▐ 删(Delete) ▐ 改(Update) ▐ 查(Select) 起别名 结果映射 开启驼峰命名(推荐) 三.MyBatis XML配置文件 ▐ 增(Insert) ▐ 删(Delete) ▐ 改(Update) ▐ 查(Select) …

PostgreSQL(二十三)TOAST技术

目录 一、TOAST简介 二、TOAST的存储方式 1、存储方式概述 2、实验&#xff1a;创建TOAST表 三、TOAST的4种压缩策略 1、策略说明 2、TOAST表额外的三个字段 四、TOAST表的计算方式 1、说明 2、实验&#xff1a;计算表大小 五、TOAST表的特点 1、优点 2、缺点 3、…

【KAN】【API教程】get_fun

抽取某个激活函数的样子 from kan import * import matplotlib.pyplot as plt # create a KAN: 2D inputs, 1D output, and 5 hidden neurons. cubic spline (k3), 5 grid intervals (grid5). model KAN(width[2,5,1], grid5, k3, seed0) x torch.normal(0,1,size(100,2)) m…

给虚拟机Ubuntu扩展硬盘且不丢数据

1.Ubuntu关机状态下先扩展&#xff0c;如扩展20GB 2.进入ubuntu&#xff0c;切换root登录&#xff0c;必须是root全选&#xff0c;否则启动不了分区工具gparted 将新的20GB创建好后&#xff0c;选择ext4,primary&#xff1b; 3.永久挂载 我的主目录在/并挂载到/dev/sda1 从图…

C++解决:早餐组合

前言 应该都知道我之前沉默了很长一段时间&#xff0c;现在慢慢想明白了&#xff0c;会继续创作&#xff0c;真的非常感谢大家对我这个幼稚小孩的支持与鼓励。 有朋友私信问我退的原因&#xff0c;在这里和大家简要说一下【狗头】 我认识一位开学初三的学长&#xff0c;他和…

H81002S 1.7mm网络变压器:BMS汽车蓝牙接收器中的超薄共模电感科技

华强盛导读&#xff1a;在当今这个日新月异的汽车科技领域&#xff0c;每一处细节都蕴含着创新与突破。作为电动汽车心脏的电池管理系统&#xff08;BMS&#xff09;&#xff0c;其高效稳定的运行不仅关乎续航与安全&#xff0c;更是智能化驾驶体验的基石。而在这背后&#xff…

有那些AI数字人制作软件?

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ 之前由于工作需要&#xff0c;要录制1 个真人讲PPT的视频&#xff0c;作为典型I人&#xff0c;本人露面是不可能的。 于是打起了数字人…