Open Feign源码深度解析

本文主要解析了Spring Cloud OpenFeign框架的源码。从框架入口开始,介绍了使用OpenFeign所需的依赖和注解,详细分析了注解配置、包扫描、客户端注册等过程,还阐述了代理对象生成、请求重试、负载均衡等逻辑,最后补充了Ribbon更新本地服务列表的机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们要研究任何一个框架的源码,必须先找到其入口,从入口处发,一步步深究,了解其调用链路,就比如今天的主角------ spring-cloud-starter-openfeign。想必对于写微服务的小伙伴应该对这个框架比较熟悉吧,但是真正细究其原理的不多,今天,我就简单聊聊我对这个框架的浅显认识,如有错误,还望批评指正。
对于OpenFeign而言,我们想要使用的话,其实很简单,只需要一个引入依赖,在配置文件中加上少量的配置即可,这要得益于Spring Boot(它的源码有机会再聊聊)的自动装配,使得我们使用其他的框架非常方便。除此之外,我们还需要用到两个注解,即:@EnableFeignClients和@FeignClient,一般而言,@EnableFeignClients注解是放在Spring Boot启动类上,具体看看这个注解,如下图所示:
在这里插入图片描述
这个注解在spring-cloud-openfeign-core包下,注解中可配置的选项较多,其中比较重要的是basePackages,可以指定是哪些包,因为它是字符串数组类型的,后面会具体讲到。同时,在@EnableFeignClients上,还有一个@Import注解,熟悉Spring框架的小伙伴们应该都知道,@Import中引入的类最终会被放入Spring容器中并被实例化,简单来讲,@Import注解中的类有三种:第一种是普通的类型,第二种是实现了ImportSelector接口的,第三种是实现了ImportBeanDefinitionRegistrar接口的,而@EnableFeignClients注解上的@Import注解中的FeignClientsRegistrar则属于第三种。因此,Spring会回调ImportBeanDefinitionRegistrar#registerBeanDefinitions()方法。因此,我们只需要看FeignClientsRegistrar#registerBeanDefinitions()即可,具体如下图所示:
在这里插入图片描述
该方法很简单,里面只调用了两个方法,FeignClientsRegistrar#registerDefaultConfiguration()方法和FeignClientsRegistrar#registerFeignClients()方法,第一个方法,实际上是解析@EnableFeignClients注解的Class<?>[] defaultConfiguration属性,如果有配置的话,就拿到这个Class的数组,最终注册到Spring容器中。这个指定的Class数组是feign的一个全局配置,因为@FeignClient注解中也有类似的配置Class<?>[] configuration,如果在某个@FeignClient上配置了configuration,当然以它自己配置的为准。一般而言,配置defaultConfiguration或者configuration不多,FeignClientsRegistrar#registerDefaultConfiguration()方法可以先过掉,不太重要。多嘴说一句,看源码,抓主线逻辑即可,有时候不用在细枝末节上纠结,细枝末节的东西有时候比较复杂,容易把自己绕进去。等到确实需要深入研究,那就需要对细节了解比较清楚。具体看FeignClientsRegistrar#registerFeignClients()方法,如下图所示:
在这里插入图片描述
这个方法中做的事也很简单,就是解析@EnableFeignClients注解中的配置,最核心的就是指定 basePackages(包扫描路径),即把扫描解析的活交给Spring来完成,除此之外,还有外一种方法,即@EnableFeignClients注解的 Class<?>[] clients,直接指明最终要生成的feign客户端,这种方式很少去用,由源码可知:二者只能选其一,如果既配置了basePackages,又配置了clients,那么配置clients优先级更高。多嘴说一句:在该方法中,明明创建了一个AnnotationTypeFilter对象(包扫描的规则),在创建ClassPathScanningCandidateComponentProvider(包扫描类,须传入包路径),调用ClassPathScanningCandidateComponentProvider#addIncludeFilter()方法的时候,又创建了一个AnnotationTypeFilter对象,这多余了。调用ClassPathScanningCandidateComponentProvider#findCandidateComponents(basePackage)方法,得到扫描后的结果,实际上就是得到了一个接口的集合,每个接口上都是被@FeignClient注解所修饰的,再遍历接口集合,解析每个接口上的@FeignClient注解,比较核心的是@FeignClient注解的value/name,它用来指定是调用的服务名,如果该服务是集群,那就需要进行负载均衡,得到某一个具体的服务实例,当然,这些都是后话。在循环中会调用到FeignClientsRegistrar#registerFeignClient()方法,看名字就知道就是注册client了,点进去看看这个方法,如下图所示:
在这里插入图片描述
该方法中,会先构建一个BeanDefinitionBuilder对象,设置 BeanClass属性为FeignClientFactoryBean.class,这个类实际上FactoryBean接口的实现类,这个操作在其他的框架中比较常见,如Mybatis,动态生成Mapper接口的实现类,也是借用了FactoryBean。具体说就是在FactoryBean中设置类型,也就是FactoryBean#getObjectType()方法返回的类型,最终FactoryBean#getObject()方法返回的也是这种类型的实例,所以就调用了BeanDefinitionBuilder#addPropertyValue()方法,指定了类型,这个方法在Spring实例化某个bean后,会调用该bean所对应的类的set方法,进行属性的赋值,因此对需要有与之对应的set方法才行(并非通过反射赋值的),对FeignClientFactoryBean而言,就是调用了FeignClientFactoryBean#setType()方法,很明显,这里的成员属性 type就是扫描的接口的类型,因为后面要根据这个类型生成真正的代理对象。
在这里插入图片描述
具体看看FeignClientFactoryBean#getObject()方法,如下图所示:
在这里插入图片描述
再看FeignClientFactoryBean#getTarget()方法,如下图所示:
在这里插入图片描述
首先是从Spring的上下文对象applicationContext中拿到FeignContext对象,这是feign自己定义的一个上下文对象,可以通过它,拿到feign的其他组件,在FeignAutoConfiguration中定义了,如下图所示:
在这里插入图片描述
再看FeignClientFactoryBean#feign()方法,如下图所示:
在这里插入图片描述
这里面从可以FeignContext中拿到feign其他组件,诸如:Encoder、Decoder、Contract等等。看看FeignClientFactoryBean#get()方法,是如何拿到这些组件的,如下图所示:
在这里插入图片描述
最终是调用了FeignContext#getInstance()方法,点进去看看这个方法,如下图所示:
在这里插入图片描述
再看看NamedContextFactory#getContext()方法,当然,NamedContextFactory是FeignContext的父类,如下图所示:
在这里插入图片描述
由此我们可以看出:在NamedContextFactory#getContext()方法中,会先去成员属性 Map<String, AnnotationConfigApplicationContext> contexts中那上下文对象,如果是第一次当然没有,那就调用NamedContextFactory#createContext()方法创建之,在这个方法中会创建AnnotationConfigApplicationContext对象,看过Spring源码的小伙伴想必很熟悉这个类了,这是Spring其中的一个上下文类(非常核心),当然,如果是用Spring Web做开发,创建的是spring其他的上下文类,对于想看spring源码的小伙伴,我推荐先看AnnotationConfigApplicationContext,当然这都是题外话了。然后把成员属性 ApplicationContext parent属性设置到了AnnotationConfigApplicationContext中 ,即调用AnnotationConfigApplicationContext#setParent()方法,其中,parent是Spring Boot的构建的上下文,并且NamedContextFactory实现了ApplicationContextAware接口,FeignContext在实例化的时候,就会回调ApplicationContextAware#setApplicationContext()方法,如下图所示:
在这里插入图片描述
这个parent属性就包括了Spring Boot中所有的对象,当然也就包括扫描自动装配的类了,当然也就包括在配置类中声明的feign组件了,具体看FeignClientsConfiguration,如下图所示:
在这里插入图片描述
总结来说,就是拿到FeignContext中的context,调用getBean()方法,如果在这个context没有对应的bean,就会再从它的属性parent中去找(AnnotationConfigApplicationContext()#getBean()方法中有此逻辑),就可以找到了。回到FeignClientFactoryBean#feign()方法,最终将得到的各个组件设置到Feign.Builder中并返回。再回到FeignClientFactoryBean#getTarget()方法中,如下图所示:
在这里插入图片描述
一般url也不会配置,则不会进入if/else中的逻辑。接着往下看,通过FeignContext获取Client和Targeter,这两个接口的实现类实际上是在FeignAutoConfiguration配置了,如下图所示:
在这里插入图片描述
Targeter有两个实现类,DefaultTargeter和HystrixTargeter,其中HystrixTargeter是和Hystrix相关,需要在引入相关依赖,才会启用,以DefaultTargeter为例(目前限流组件都是使用的sentinel,这个框架我后面聊聊,所以不考虑Hystrix),再看看Client:
在这里插入图片描述
FeignAutoConfiguration中配置了两个Client的实现类,即ApacheHttpClient和OkHttpClient,以ApacheHttpClient为例,它是声明在FeignAutoConfiguration.HttpClientFeignConfiguration配置类中的,但是该类上有一个注解,即@ConditionalOnMissingClass(“com.netflix.loadbalancer.ILoadBalancer”),表示只有当缺失了 com.netflix.loadbalancer.ILoadBalancer接口(即没有对应的依赖),上个博客,我讲的是ribbon,有印象的小伙伴应该知道,ILoadBalancer接口是ribbon包下的接口,我们在微服务开发的时候,一般是feign和ribbon配合使用的,这两个框架的依赖都会引入,所以,FeignAutoConfiguration.HttpClientFeignConfiguration不会被Spring扫描到Spring容器中,因为不满足条件注解的条件,所以ApacheHttpClient和OkHttpClient这两个Client接口的实现类也就不会被实例化到Spring,我们再看看,Client的实现类到底是哪个呢?我们继续往下看,仔细观察的话,会发现spring-cloud-openfeign-core下的org.springframework.cloud.openfeign包下有一个ribbon包,这会不会是跟ribbon做整合的呢,展开ribbon包,各位小伙伴应该都知道,ribbon是用来做客户端负载均衡的,所以类名中带"LoadBalance"的类我们需要重点关注,刚好在ribbon包下,就看到了看到有两个比较特殊的类,即:OkHttpFeignLoadBalancedConfiguration和HttpClientFeignLoadBalancedConfiguration,这两个配置类看起来像我们要找的,可以点进去看一下,反正源码这个东西,有些时候需要大胆猜一下,如果你看源码有经验的话,大概率是可以猜对的,就算猜错了,再继续找呗,找不到就直接打断点去找。以OkHttpFeignLoadBalancedConfiguration为例,如下图所示:
在这里插入图片描述
返回的LoadBalancerFeignClient就是Client的实现类,并且名字带有"LoadBalancer",肯定有负载均衡的功能,应该还是借助到了ribbon框架,再看OkHttpFeignLoadBalancedConfiguration上的条件注解有两个,分别是:@ConditionalOnProperty(“feign.okhttp.enabled”),是需要在配置文件中启用的,即:feign.okhttp.enabled=true,@ConditionalOnClass(OkHttpClient.class),表示引入了OkHttp相关的依赖,才会有OkHttpClient.class。再看OkHttpFeignLoadBalancedConfiguration#feignClient()方法,上面也有一个条件注解@ConditionalOnMissingBean(Client.class),表示此时在Spring容器中没有Client实现类bean对象,也就是程序员没有自动在某个配置类中声明一个Client的实现类(一般我们也不会这么做)。HttpClientFeignLoadBalancedConfiguration也是一样。我们只需要在OkHttp或者HttpClient依赖中的一个并在配置文件中启用即可,到这里还没完,在OkHttpFeignLoadBalancedConfiguration#feignClient()方法中,有三个入参,也需要看看。SpringClientFactory类似FeignContext,是ribbon获取其组件的一个上下文,这两个类的父类都是NamedContextFactory,okHttpClient自不必说,真正发起远程调用的的http 客户端,还有一个是CachingSpringLoadBalancerFactory cachingFactory,点进来看看,如下图所示:
在这里插入图片描述
很明显CachingSpringLoadBalancerFactory使用的工厂模式,通过CachingSpringLoadBalancerFactory#create()方法,得到FeignLoadBalancer对象,而CachingSpringLoadBalancerFactory是在FeignRibbonClientAutoConfiguration类中声明的,如下图所示:
在这里插入图片描述
FeignRibbonClientAutoConfiguration是在spring.factories中定义好了,Spring Boot在启动的时候就会加载该类(Spring Boot框架的SPI机制),然后通过@Import注解引入了上文我们提到的HttpClientFeignLoadBalancedConfiguration和OkHttpFeignLoadBalancedConfiguration等。与此同时,FeignRibbonClientAutoConfiguration中有两个方法都会返回CachingSpringLoadBalancerFactory,当然其中只会有一个生效,依据方法上的条件注解,这两个方法返回的CachingSpringLoadBalancerFactory对象,一个带有重试功能,另一个则没有。假设我们没有引入org.springframework.retry.support.RetryTemplate相关的依赖的话,那Spring最终调用的就是FeignRibbonClientAutoConfiguration#cachingLBClientFactory()方法,得到的CachingSpringLoadBalancerFactory对象。再回看CachingSpringLoadBalancerFactory,就知道,最终得到了FeignLoadBalancer,调用的是它的有参构造,其中ILoadBalancer,就是我上篇博客中,讲ribbon,ILoadBalancer的实现类ZoneAwareLoadBalancer。因此总结来说:Client的实现类就是LoadBalancerFeignClient,而LoadBalancerFeignClient中的属性是CachingSpringLoadBalancerFactory lbClientFactory,最终通过CachingSpringLoadBalancerFactory#create()方法,得到的是FeignLoadBalancer,而FeignLoadBalancer中的ILoadBalancer lb属性就是ZoneAwareLoadBalancer。再回到FeignClientFactoryBean中,在FeignClientFactoryBean#getTarget()方法中,调用了targeter#target()方法,生成了代理对象,而targeter实现类则是DefaultTargeter,具体看DefaultTargeter#target()方法,如下图所示:
在这里插入图片描述
再看Feign.Builder#target()方法,如下图所示:
在这里插入图片描述
最终就是调用ReflectiveFeign#newInstance()方法,如下图所示:
在这里插入图片描述
这里使用的JDK的动态代理,因此,只要确定了InvocationHandler就可以了,最终的逻辑一定在InvocationHandler#invoke()方法中做的。看看ReflectiveFeign的成员属性:targetToHandlersByName和factory的具体类到底是是哪个,还是要回到上一级,即ReflectiveFeign#newInstance()方法中,在Feign.Builder#build()方法中,就做了赋值,如下图所示:
在这里插入图片描述
其中handlersByName是ParseHandlersByName对象,invocationHandlerFactory是Feign.Builder InvocationHandlerFactory invocationHandlerFactory,而Capability#enrich()方法只是做了一些简单的处理,这个就不细看了,具体看看invocationHandlerFactory是在哪里被赋值的,如下图所示:
在这里插入图片描述
实际上在构造方法中赋值的,并且是一个默认的对象,点进去看看,如下图所示:
在这里插入图片描述
再回到ReflectiveFeign#newInstance()方法中,首先是调用了ReflectiveFeign.ParseHandlersByName#apply()方法,如下图所示:
在这里插入图片描述
核心的地方是factory#create()方法:得到一个MethodHandler对象,如下图所示:
在这里插入图片描述
得到的是MethodHandler接口的实现类SynchronousMethodHandler,并放入result集合中返回。再看ReflectiveFeign#newInstance()方法,如下图所示:
在这里插入图片描述
就可知,想要得到InvocationHandler,就需要看factory#create()方法,而就是前文提到的InvocationHandlerFactory,最终得到的是ReflectiveFeign.FeignInvocationHandler对象。最终,我看只需要看ReflectiveFeign.FeignInvocationHandler#invoke()方法即可,如下图所示:
在这里插入图片描述
而dispatch属性实际上就是是一个Map,缓存的value就是SynchronousMethodHandler对象,再看看SynchronousMethodHandler#invoke()方法,如下图所示:
在这里插入图片描述
而最核心的方法是SynchronousMethodHandler#executeAndDecode()方法,该方法是被try/catch所包裹的,如果抛异常,则通过retryer对象,进行重试,如果是用的默认重试策略,由下图可知是1秒充实一次,默认重试五次,你也可以自定义重试策略,在配置类中声明一个Retryer接口的实现类,实现对应的方法即可。
在这里插入图片描述
再看SynchronousMethodHandler#executeAndDecode()方法,如下图所示:

在这里插入图片描述
Client属性实际上就之前在FeignClientFactoryBean传入的,再层层往下传,如下图所示:
在这里插入图片描述
client就是LoadBalancerFeignClient对象,最后看看LoadBalancerFeignClient#execute()方法,如下图所示:
在这里插入图片描述
再看LoadBalancerFeignClient#lbClient()方法,如下图所示:
在这里插入图片描述
这个lbClientFactory属性,不就是我们创建LoadBalancerFeignClient,传入的CachingSpringLoadBalancerFactory对象吗,而CachingSpringLoadBalancerFactory#create()方法,返回的则是FeignLoadBalancer对象,最终调用的是AbstractLoadBalancerAwareClient#executeWithLoadBalancer()方法,AbstractLoadBalancerAwareClient就是FeignLoadBalancer的父类,如下图所示:
在这里插入图片描述
AbstractLoadBalancerAwareClient#reconstructURIWithServer()返回了一个url,并且名字叫finalUri,可以猜测,可能url已经是替换成真正ip/port的请求地址了,并且该方法还有一个入参Server,这个Server极有可能是我们要请求的具体某个服务节点,看看Server是从哪里传入的,实际上就是调用的LoadBalancerCommand#submit()方法,如下图所示:
在这里插入图片描述
就看到了非常非常重要的一个方法,即:LoadBalancerCommand#selectServer(),如下图所示:
在这里插入图片描述
LoadBalancerCommand.this.loadBalancerContext#getServerFromLoadBalancer()方法,这个方法名就很清晰了,点进去看一下,如下图所示:
在这里插入图片描述
基本上看到这里,也就是图中框的地方,就不用再往下去看了,无非是ribbon的负载均衡逻辑了,最终按照一定的规则得到一个Server,如随机、轮询、加权轮询、最小活跃数…,最后再回到AbstractLoadBalancerAwareClient#executeWithLoadBalancer()方法中,如下图所示:
在这里插入图片描述
无非是通过Server拿到实际的ip/port替换服务名,再组装Request,最终通过http客户端发起远程请求,得到响应,再返回一个Response,如下图所示:
在这里插入图片描述
以上,就是 Open Feign的源码,感谢您各位耐心看到这里,然而下面还有东西…

附:
再多聊一点,ribbon是如何实现更新自己的本地服务列表的,上个博客聊 ribbon源码的时候,没有聊到这块,这里补充一下:
进入到ZoneAwareLoadBalancer的父类,即DynamicServerListLoadBalancer,看名字也知道他是干啥的,在它的父类:BaseLoadBalancer中有两个属性:List allServerList 和 List upServerList,前一个是拉取的所有服务列表,后一个是可用的服务列表。有一个BaseLoadBalancer#setServersList()方法,用于设置服务列表,截取核心的方法块,如下图所示:
在这里插入图片描述
allServers是setServersList()方法传入的,然后会赋值给其成员属性allServerList,如果BaseLoadBalancer的ping不为空,则调用BaseLoadBalancer#forceQuickPing()方法,如下图所示:
在这里插入图片描述
再看Pinger#runPinger()方法,如下图所示:
在这里插入图片描述
pingerStrategy.pingServers()方法中就是遍历allServerList,拿到每一个Server的ip/port去ping,ping通就返回true,反之返回false,在最终将每一个true/false的结果设置到Server的 isActive中。upServerList中只有isActive为true的Server。
Server列表可以通过BaseLoadBalancer#getServerList()方法拿到,传入true/false,frue => upServerList, false => allServerList
在这里插入图片描述
再看看BaseLoadBalancer#setServersList()方法是在子类中被调用的,如下图所示:
在这里插入图片描述
在这里插入图片描述
在DynamicServerListLoadBalancer的有参构造中,会初始化updateAction属性,即内部类 NamelessClass_1对象,然后调用DynamicServerListLoadBalancer#initWithNiwsConfig()方法,在该方法中,再调用DynamicServerListLoadBalancer#restOfInit()方法,在该方法中,再调用DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature()方法,在该方法中,如下图所示:
在这里插入图片描述
在DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature()方法中, 调用serverListUpdaterServerListUpdater#start()方法,而serverListUpdater属性实在构造中赋值的,再看看其子类ZoneAwareLoadBalancer,是怎么赋值的,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
再看看ZoneAwareLoadBalancer被实例化的地方,即RibbonClientConfiguration配置类中 ,如下图所示:
在这里插入图片描述

重点看看ServerListUpdater接口的实现类,PollingServerListUpdater#start()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
可知:initialDelayMs = 1000ms,refreshIntervalMs = 30000ms ,该定时任务,延期1s,每隔30s(默认配置,可修改)。调用的是wrapperRunnable#run()方法,在该方法内部调用的就是updateAction#doUpdate()。也就是NamelessClass_1#doUpdate()方法,最终调用到了DynamicServerListLoadBalancer#updateListOfServers()方法,如下图所示:
在这里插入图片描述
剩下就不用多说了。总结来说就是:ribbon默认每隔30s,就默认像注册中心拉取服务列表,并赋值给allServerList属性,这就是所谓的本地注册列表,与此同时,还会通过ping,去过滤掉”不健康”的服务,得到upServerList服务列表,供ribbon在客户端做负载均衡。以RandomRule为例,就可以看到它内部调用了getReachableServers()方法,如下图所示:
在这里插入图片描述
以上,如有错误,恳请批评指正!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值