SpringCloud OpenFeign 源码解析

SpringCloud OpenFeign 源码解析

  • 核心组件与概念
  • 动态注册BeanDefiniation
  • 实例初始化
  • 函数调用和网络请求

阅读OpenFeign 源码时,可以沿着两条路线进行, 一是FeignServiceClient 这样的被@FeignClient
注解修饰的接口类(后续简称为F eignClient 接口类)如何创建, 也就是其Bean 实例是如何被创建的;
二是调用FeignServiceClient 对象的网络请求相关的函数时,OpenFeign 是如何发送网络请求的。而OpenFeign
相关的类也可以以此来进行分类,一部分是用来初始化相应的Bean 实例的,一部分是用来在调用方法时发送网络请求。

图5-2 是OpenFeign 相关的关键类图,其中比较重要的类为Fe ignClientFactoryBean 、FeignContxt 和SynchronousMethodHandler 。FeignClientFactoryBean 是创建@FeignClient 修饰的接口类Bean 实例的工厂类; FeignContext 是配置组件的上下文环境,保存着相关组件的不同实例,这些实例由不同的FeignConfiguration 配置类构造出来;SynchronousMethodHandler是MethodHandler 的子类,可以在FeignClient 相应方法被调用时发送网络请求,然后再将请求响应转化为函数返回值进行输出。

在这里插入图片描述

动态注册Bean Definition

OpenFeign 可以通过多种方式进行自定义图5 - 3 源码流程图配置,配置的变化会导致接口类初始化时使用不同的Bean 实例,从而控制OpenFeign 的相关行为,比如说网络请求的编解码、压缩和日志处理。可以说,了解OpenFeign 配置和实例初始化的流程与原理对于我们学习和使用OpenFeign 有着至关重要的作用,而且SpringCloud 的所有项目的配置和实例初始化过程的原理基本相同,了解了OpenFeign 的原理,就可以触类旁通,一通百通了。

FeignClientsRegistrar
@EnableFeignClients 的基本作用,它就像是OpenFeign 的开关一
样, 一切OpenFeign 的相关操作都是从它开始的。@EnableFeignClients 有三个作用, 一是引入FeignClientsRegistrar ;二是指定扫描i Feign Client 的包信息,就是指定FeignClient 接口类
所在的包名; 三是指定F eignClient 接口类的自定义配置类。@EnableFeignClients 注解的定义如下所示:
FeignClientsRegistrar 是ImportBeanDefinitionRegistrar 的子类, Spring 用ImportBeanDefinitionRegistrar 来动态注册BeanDefinition 。OpenFeign 通过FeignClientsRe~istrar来处理@F eign Client 修饰的FeignClient 接口类, 将这些接口类的BeanDefinition 注册到
Spring 容器中,这样就可以使用@Autowired 等方式来自动装载这些Feign Client 接口类的Bean 实例。FeignClientsRegistrar 的部分代码如下所示:

在这里插入图片描述
FeignClientsRegistrar 的registerBeanDefinitions 方法主要做了两个事情, 一是注册@EnableFeignClients 提供的自定义配置类中的相关Bean 实例, 二是根据@EnableFeignC!ients 提供的包信息扫描@Feign Client 注解修饰的FeignC!eint 接口类,然后进行Bean 实例注册。
@EnableFeignClients 的自定义配置类是被@Configuration 注解修饰的配置类,它会提供一系列组装FeignClient 的各类组件实例。这些组件包括: Client 、Target、Decoder 、Encoder 和Contract 等。接下来看看registerDefaultConfiguration 的代码实现, 如下所示:

//FeignClientsRegiStrar. java
private void registerDefaultConf 工guration(Annotat 工onMetadata metadata ,
BeanDefinitionRegistry registry) {
//获取到metadata 中关于EnableFe 工gnClients 的属性值键值对
Map<String , Object> defaultAttrs = metadata
. getAnnotationAttributes(EnableFeignClients . class . getName(), true) ;
//如果EnableFeignClients 配置了defaultConfigurat 工on 类,那么才进行下一步操作,如果没有,
会使用默认的FeignConfiguration
if (defaultAttrs != null && defaultAttrs . containsKey ( ” defaultConfiguration ” )){
String name ;
if (metadata . hasEnclosingClass () ) {
name =default . " + metadata.getEnclosingClassName() ;
else {
name =default J ’ + metadata .getClassName() ;
registerCl 工entConf igurat 工on(reg 工stry , name ,
defaultAttrs .get("defaultConfiguration” )) ;
}
}

registerD巳faultConfiguration 方法会判断@EnableFeignClients 注解
是否设置了defaultConfiguration 属性。如果有,则将调用registerC!ientConfiguration 方法,进行BeanDefinitionRegistry 的注册。

BeanDefinitionRegistry 是Spring 框架中用于动态注册BeanDefinition 信息的接口,调用其registerBeanDefinition 方法可以BeanDefinition 注册到Spring 容器中,其中name 属性就是注册BeanDefinition 的名称。

扫描类信息
FeignClientsRegistrar 做的第二件事情是扫描指定包下的类文件,注册@FeignClient 注解修饰的接口类信息,如下所示:
FeignClientsRegistrar 的registerFeignClients 方法依据@EnableFeignClients的属性获取要扫描的包路径信息,然后获取这些包下所有被@FeignC!ient 注解修饰的接口类的BeanDefinition , 最后调用registerFeignClient 动态注册BeanDefinition 。

registerFeignClients 方法中有一些细节值得认真学习,有利于加深了解Spring 框架。首先是如何自定义Spring 类扫描器,即如何使用ClassPathScanningCandidateComponentProv i der和各类TypeFilter 。
OpenFeign 使用了AnnotationTypeFilter ,来过滤出被@F eignClient 修饰的类, getScanner方法的具体实现如下所示:
在这里插入图片描述

实例初始化

FeignClientFactoryBean 是工厂类, Spring 容器通过调用它的getObject 方法来获取对应的Bean 实例。被@FeignClient 修饰的接口类都是通过FeignClientFactoryBean 的getObject方法来进行实例化的,具体实现如下代码所示:

//FeignClientFactoryBean . Java
public Object getObject( ) throws Except 工0口{
FeignContext context = applicationContext.getBea 口( FeignContext class);
Feign.Builder builder= feign(context);
工f (StringUtils . hasText(th 工s . url) && ! this . url.startsWith( "http ”)){
this . url = ” http : // ” + this.url ;
}
String url = this.url + cleanPath() ;
//调用Fe 工gnContext 的get Instance方法获取Client 对象
Client client = getOptional(context , Client . class) ;
//因为有具体的Url ,所以就不需要负载均衡, 所以除去LoadBalancerFeignClient 实例
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ( (LoadBalancerFe 工gnClient)client) getDelegate();
builder.client(client) ;
Targeter targeter = get(context , Targeter.class) ;
return targeter . target(this , bu 工lder , co口text , new HardCodedTarget<>(
this type , this.name , url)) ;
}

这里就用到了FeignContext 的getlnstance 方法, 我们在前边已经讲解了FeignContext的作用, getOptional 方法调用了FeignContext 的getlnstance 方法,从Feign Context 的对应名称的子上下文中获取到Client 类型的Bean 实例,其具体实现如下所示:
在这里插入图片描述

函数调用和网络请求

在配置和实例生成结束之后,就可以直接使用FeignClient 接口类的实例,调用它的函数来发送网络请求c 在调用其函数的过程中,由于设置了MethodHandler ,所以最终函数调用会执行SynchronousMethodHandler 的invoke 方法。在该方法中,OpenFeign 会将函数的实际参数值与之前生成的Req uestTemplate 进行结合,然后发送网络请求。
OpenFeign 发送网络请求时几个关键类的交互流程图,大概分为三个阶段: 一是将函数实际参数值添加到RequestTemplate 中; 二是调用Target 生成具体的Request 对象;三是调用C lient 来发送网络请求,然后将Response 转化为对象进行返回。

在这里插入图片描述
SynchronousMethodHandler 的invoke 方法先创建了RequestTemplate对象。在该对象的创建过程中,使用到之前收集的函数信息MethodMetadata 。遍历MethodMetadata 中参数相关的indexToName , 然后根据索引从invoke 的参数数组中获得对应的值,将其填人对应的键值对中。然后依次处理查询和头部相关的参数值。invoke 方法调用RequestTemplate.Factory 的create 方法创建RequestTemplate对象.
Client 是用来发送网络请求的接口类,有OkHttpClient RibbonClient 两个子类。Okh 即Client 调用okhIttp 的相关组件进行网络请求的发送。OkHttpClient 的具体实现如下所示:

//OkHttpClient . java
public feign.Response execute(feign . Request i口put, feign . Request Options options)
throws IOException {
//将feign . Request 转换为Oktthp 的Request 对象
Request request = toOkHttpRequest (Input);
//使用Okhttp 的同步操作发送网络请求
Response response = requestOkHttpClient . newCall(request) . execute() ;
//将Okhttp 的Response 转换为feign.Response
return toFeignResponse(response) . toBuilder() . request(input) . bu ild() ;
}

写这篇真的花了很久的功夫,但是希望对大家有用,我也就没有白努力,最后记得 点赞,收藏,关注 一键三连
微信搜索【码上代码】文章同步更新,更有1000道互联网架构师面试题等你,我们一起进大厂

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码上代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值