0 前言
本文是对上一篇文章的补完,旨在明确以下问题:
- feign如何代理本地方法的调用;
- http调用参数是如何设置的;
- 注册中心的服务数据是如何与feign关联的;
- feign的负载均衡的实现。
1 FeignClient对象注入
上一篇文章说过,@FeignClient
注解的对象基于FeignClientFactoryBean
注册,那么我们分析feign的调用流程就从这个类开始:
1 | class FeignClientFactoryBean |
FeignClientFactoryBean
实现了FactoryBean<Object>
接口,那么@FeignClient
注解的bean注入时依赖于实现的getObject()
方法。从上述源码可以看到该方法调用了getTarget()
方法,那么我们来看看getTarget()
的具体实现:
1 | <T> T getTarget() { |
1.1 feign(FeignContext context)
首先来看builder
的创建,调用了feign(context)
,来看一下
1 | protected Feign.Builder feign(FeignContext context) { |
这里涉及两个重要方法get(FeignContext context, Class<T> type)
和configureFeign(FeignContext context, Feign.Builder builder)
,接着逐一来看一下。
1.1.1 get(FeignContext context, Class type)
1 | protected <T> T get(FeignContext context, Class<T> type) { |
可以看到调用了context.getInstance(this.contextId, type)
,该方法继承自FeignContext
的父类NamedContextFactory
,来看一下
1 | public <T> T getInstance(String name, Class<T> type) { |
1 | protected AnnotationConfigApplicationContext getContext(String name) { |
根据上述两个方法可以看出当在contexts
中根据contextId
找不到对应实例时,将会调用createContext(name)
创建一个加入到contexts
中,那么我们来看看createContext(name)
方法:
1 | protected AnnotationConfigApplicationContext createContext(String name) { |
该方法从configurations
中分别找到指定的FeignClient
注解类和EnableFeignClients
注解的类,并注册到context
中,返回一个AnnotationConfigApplicationContext
类型对象。最终通过context.getBean(type)
方法,得到已经创建好的bean对象。
1.1.2 configureFeign(FeignContext context, Feign.Builder builder)
回到feign(FeignContext context)
方法中接着来看configureFeign(FeignContext context, Feign.Builder builder)
的流程:
1 | protected void configureFeign(FeignContext context, Feign.Builder builder) { |
这里主要关注configureUsingConfiguration
:
1 | protected void configureUsingConfiguration(FeignContext context, |
该方法从context
中为builder
赋值logLevel
、retryer
、errorDecoder
等属性,这里需要说明的是retryer
的默认实现如下所示,即默认关闭重试机制。
1 | @Bean |
1.2 loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget target)
1 | if (!StringUtils.hasText(this.url)) { |
回到getTarget()
方法,分析完feign(context)
创建builder
的流程后,来看上述代码段,该情况对应的是@FeignClient
注解没有设置url
值,也就是没有设置请求的绝对路径的情况。这种情况下得到的url
就是类似http://name
这样的值,这就需要调用loadBalance
方法来处理,来看一下该方法:
1 | protected <T> T loadBalance(Feign.Builder builder, FeignContext context, |
可以看到这里得到了一个Client
类型的实例,由DefaultFeignLoadBalancedConfiguration
类注入,具体源码如下:
1 | @Configuration |
接着还得到了Targeter
类型的实例,该对象上一篇文章中有分析过,根据feign.hystrix.HystrixFeign
类存在与否会初始化Targeter
为HystrixTargeter
或者DefaultTargeter
类型,这里为了简化分析暂不考虑Hystrix相关内容,就以DefaultTargeter
做为实现进行分析,来看看该类的target(this, builder, context, target)
方法做了什么。
1 |
|
调用了Feign.Builder
的target
方法:
1 | public <T> T target(Target<T> target) { |
那么先来看一下build
方法
1 | public Feign build() { |
这里主要都是些初始化的工作,创建并返回一个ReflectiveFeign
类型的对象,其中Feign.Builder
对传入的contract
、options
和encoder
等参数均做了初始化,具体如下:
1 | private final List<RequestInterceptor> requestInterceptors = |
2 代理对象创建
接着来看ReflectiveFeign
对象的newInstance
方法:
1 | @Override |
方法的前半部分主要是通过for循环将nameToHandler
中的方法(即@FeignClient
注解类下的方法)一一注册到methodToHandler
中。
后半部分,熟悉JDK动态代理的话一下就会发现,这里是在通过动态代理方式创建代理对象proxy
,至于代理对象的具体实现就要来看下factory.create(target, methodToHandler)
了,这里的factory
是之前ReflectiveFeign
初始化时传入的InvocationHandlerFactory
对象,其具体定义为:
1 | public interface InvocationHandlerFactory { |
这里可以看到其create
方法的具体实现,跟进来看一下这个FeignInvocationHandler
类。
3 代理方法调用
1 | static class FeignInvocationHandler implements InvocationHandler { |
该类继承了InvocationHandler
接口,根据jdk的动态代理的实现,该类就是代理的实现类,当调用代理接口的方法,即@FeignClient
注解类的方法时,最终都会由该FeignInvocationHandler
对象的invoke
方法来处理。
3.1 apply(Target key)
可以看到这里的实际业务处理调用的是dispatch.get(method).invoke(args)
。这里的dispatch
即是之前提到过的methodToHandler
对象,其通过targetToHandlersByName.apply(target)
创建,来看一下该方法:
1 | public String, MethodHandler> apply(Target key) { < |
这里返回的是一个map对象,其key为类名加方法名的组合,用于唯一标识一个方法,value通过factory.create(key, md, buildTemplate, options, decoder, errorDecoder)
生成,其中根据方法的参数和方法体情况不同会创建不同的buildTemplate
。这里的factory
是之前通过SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy);
创建的,我们来看看其create
方法
1 | public MethodHandler create(Target<?> target, |
最后返回的value值即是一个SynchronousMethodHandler
对象,那么dispatch.get(method).invoke(args)
调用的就是该对象的invoke
方法,来看一下:
1 |
|
3.2 executeAndDecode(RequestTemplate template)
该方法实质是创建了一个template
对象,然后调用executeAndDecode(template)
方法:
1 | Object executeAndDecode(RequestTemplate template) throws Throwable { |
这里的核心是client.execute(request, options)
的调用,因为client
是LoadBalancerFeignClient
的实例,那么需要看一下它的execute
方法:
1 | @Override |
分别来看一下其中最核心的getClientConfig
和executeWithLoadBalancer
方法
3.2.1 getClientConfig
1 | IClientConfig getClientConfig(Request.Options options, String clientName) { |
这里的clientFactory
是SpringClientFactory
的实例,其getClientConfig
方法为
1 | public IClientConfig getClientConfig(String name) { |
1 | @Override |
可以看到其实际调用的是super.getInstance(name, type)
,也就是父类NamedContextFactory
的getInstance
方法,这个方法之前已经分析过了,所以很自然的知道getClientConfig
最后返回的是IClientConfig
类型的bean对象,该bean的初始化在RibbonClientConfiguration
类中,源码如下:
1 | @Bean |
这里的DefaultClientConfigImpl
类中已经定义好了大部分http请求所需参数,如MaxTotalHttpConnections
、PoolKeepAliveTime
、MaxAutoRetries
等,而从代码也可以看出ConnectTimeout
、ReadTimeout
和GZipPayload
三个参数是单独设置的。
3.2.2 executeWithLoadBalancer
首先lbClient(clientName)
返回的是一个FeignLoadBalancer
的实例,那么我们重点关注其executeWithLoadBalancer(ribbonRequest, requestConfig)
的实现:
1 | public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { |
可以看到,这里通过AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)
方法即通过http请求方式获取到请求返回值,由于请求不是我关注的重点暂不深入分析,我关注的是server
的获取,那么需要继续分析submit
方法(这里开始大量使用了rxjava进行异步编程,暂不做讲解):
1 | public Observable<T> submit(final ServerOperation<T> operation) { |
方法较长,取了其中为server
赋值的一段,可以看到当server
为空时,会调用selectServer()
进行获取:
1 | private Observable<Server> selectServer() { |
这里的server
由loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey)
创建:
1 | public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { |
该方法操作的核心是lb
对象,该对象的allServerList
属性包含了从注册中心获取的对应服务的所有地址列表,该实例在RibbonClientConfiguration
中初始化:
1 |
|
顺带一提这里的serverList
是注册中心的地址列表,并且其类型根据注册中心不同而有所不同,例如我使用的是Nacos,该类就是NacosServerList
的实例。其他注册中心相关内容暂不做深入,所以继续来看lb.chooseServer(loadBalancerKey)
:
1 | @Override |
ZoneAwareLoadBalancer
支持多区域的负载均衡,但这里我们默认以最常见的单个区域情况进行分析,可以看到调用了父类BaseLoadBalancer
的chooseServer
方法:
1 | public Server chooseServer(Object key) { |
这里的rule
对象是实现请求负载均衡的核心,其默认的实现为(如需要其他的负载均衡实现需要自行进行IRule
的注入):
1 |
|
ZoneAvoidanceRule
类继承了ClientConfigEnabledRoundRobinRule
,因此其本质是实现了轮询规则,其choose
方法实现为:
1 |
|
主要实现在chooseRoundRobinAfterFiltering
方法中:
1 | public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) { |
到这里就很明确了,该方法通过incrementAndGet方式进行计数以轮询方式从servers
中获取可访问的Server
对象。
4.总结
通过上述流程的分析基本了解了开篇提出的4个问题,对Feign有了一个大概的了解,也知道了如何对Feign进行一些个性化的配置,但我们知道Feign包含了Ribbon和Hystrix的功能,目前的分析中并未涉及Hystrix,这块也是后面有时间的时候要补上的内容。