花一个周末,掌握 SpringCloud OpenFeign 核心原理

发表于 3年以前  | 总阅读数:306 次

前言

现在的微服务在互联网圈子里应用已经相关广泛了,SpringCloud 是微服务领域当之无愧的 "头牌"

加上现在的一些轮子项目,新建一个全套的 SpringCloud 项目分分钟的事情,而我们要做的事情,就是不把认知停留在使用层面,所以要深入到源码中去理解 SpringCloud

为什么要选择 OpenFien? 因为它足够的 "小",符合我们的标题:一个周末搞定

Feign 的源代码中,Java 代码才 3w 多行,放眼现在热门的开源项目,包括不限于 Dubbo、Naocs、Skywalking 中 Java 代码都要 30w 行起步

通过本篇文章,希望读者朋友可以掌握如下知识

  • 什么是 Feign
  • Feign 和 Openfeign 的区别
  • OpenFeign 的启动原理
  • OpenFeign 的工作原理
  • OpenFeign 如何负载均衡

spring-cloud-starter-openfeign version:2.2.6.RELEASE

什么是 Feign

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

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

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

Feign 和 Openfeign 的区别

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

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

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

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

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 老大哥同时集成了 Ribbon 和 Eureka 以及 Spring Cloud LoadBalancer,以在使用 Feign 时提供负载均衡的 HTTP 客户端

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

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

环境准备

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

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

生产者服务

添加 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;
        }
    }
}

消费者服务

定义 FeignClient 消费服务接口

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

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

@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("公号-源码兴趣圈");
        return result;
    }
}

Feign 的启动原理

我们在 SpringCloud 的使用过程中,如果想要启动某个组件,一般都是 @Enable... 这种方式注入,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 的

注入@Import

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

ResourceLoaderAware、EnvironmentAware 为 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);
    // 扫描 packge,注册被 @FeignClient 修饰的接口类为 IOC Bean
    registerFeignClients(metadata, registry);
}

添加全局配置

registerDefaultConfiguration 方法流程如下

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

注册 FeignClient 接口

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

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

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

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

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

Feign 的工作原理

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

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

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 中被成功创建

初始化父子容器

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

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

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

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

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

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

如法炮制,Feign.Builder、Encoder、Decoder、Contract 都可以通过子容器获取对应 Bean

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

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

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

动态代理生成

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

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

Client:Feign 发送请求以及接收响应等都是由 Client 完成,该类默认 Client.Default,另外支持 HttpClient、OkHttp 等客户端

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

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

创建反射类 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 中很常见,Hystix 源码底层也有使用

网络调用默认使用 HttpURLConnection,可以配置使用 HttpClient 或者 OkHttp

调用远端服务后,再将返回值解析正常返回,到这里一个完成的 Feign 调用链就聊明白了

Feign 如何负载均衡

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

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

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

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

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

结语

文章从最基础的知识介绍什么是 Feign?继而从源码的角度上说明 Feign 的底层原理,总结如下:

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

虽然 Feign 体量相对小,但是想要一篇文章完全描述,也不太现实,所以这里都是挑一些核心点讲解,没有写到的地方还请见谅

另外,由于作者水平有限, 欢迎大家能够反馈指正文章中错误不正确的地方, 感谢

参考文章:

  • https://blog.csdn.net/forezp/article/details/73480304
  • https://www.cnblogs.com/yangxiaohui227/p/12965340.html
  • https://www.cnblogs.com/crazymakercircle/p/11965726.html

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/zYfhJDyHji58Skw3IqIWzg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237276次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8114次阅读
 目录