19. SpringCloud之Feign源码解析

image.png

1、前言

Feign的功能和使用方式 可以看这篇 :

SpringCloud之Feign使用介绍

可以看到 @FeignClient 是作用在接口上的, 并没有实现类。这一点和mybatis的Mapper接口一样,肯定是利用动态代理 来 实现的。

还有Feign是集成了Ribbon和Hystrix组件的。

接下来我们到Feign源码里一探究竟。

2、源码入口�

除了导入依赖之外。一般我们会用 @EnableFeignClients 来启动Feign功能。

@SpringBootApplication
@EnableFeignClients(clients = OrderService.class)
public class MicroUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(MicroUserApplication.class);
    }
}

@EnableFeignClients注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 源码入口
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

   String[] value() default {};
   // 扫描的包下的含有@FeignClient的类,创建Feign客户端
   String[] basePackages() default {};
  
     // 扫描类所处在的包下的含有@FeignClient的类,创建Feign客户端
   Class<?>[] basePackageClasses() default {};
   
   Class<?>[] defaultConfiguration() default {};
   // 开启Feign客户端的接口类
   Class<?>[] clients() default {};
}

里面有一些配置开启Feign客户端的接口类,以及要扫描的包的属性。

最重要的是会用@Import 注解导入 FeignClientsRegistrar类。

image.png

FeignClientsRegistrar类,会实现ImportBeanDefinitionRegistrar接口,那么就必定会实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry)方法。在Spring容器把FeignClientsRegistrar导入,实例化,之后调用。

AnnotationMetadata就是导入他的类的元数据对象。主要是用来获取@EnableFeignClients注解里的具体配置。

然后在 registerBeanDefinitions方法中根据配置,注册Feign客户端。

3、Feign客户端的注册

看下FeignClientsRegistrar的registerBeanDefinitions方法逻辑。

会调用registerFeignClients方法,传入的AnnotationMetadata对象就是类上有 @EnableFeignClients的启动类MicroUserApplication。可以看到里面是有注解对象的。

image.png

3.1、根据@EnableFeignClients注解的配置,确定类扫描器要扫的包路径 以及过滤条件

进入registerFeignClients。

创建一个扫描器。

并且从元数据获取@EnableFeignClients注解的配置信息。

image.png

还会创建一个扫描器在扫描包的类的时候 过滤注解类型的过滤器。

指定有@FeignClient 注解的类才是有效的。

image.png

接下来获取@EnableFeignClients注解里的clients属性。

如果没有配置,那就获取basePackage属性,扫描器用的过滤器 就是 过滤@FeignClient注解的过滤器。 也就是 扫描 basePackage下含有 @FeignClient注解的类

image.png

否则,就会 根据@EnableFeignClients注解里的clients属性配置的类,去获取配置的类所在的包, 新建的过滤器 只过滤 clients属性配置的这些类。

也就是 只扫描 出clients属性配置的这些类。

在spring源码里@ComponentScan的原理里也是 这样, 同样有扫包用的扫描器,扫描器里有过滤器,用来扫指定包下含有@Component注解的类。

image.png

3.2、扫描包,过滤之后返回BeanDefinition对象集合

从@EnableFeignClients注解的配置信息,确定好要扫的包路径以及过滤条件后。开始用扫描器扫包,扫描出来符合条件的类,返回BeanDefinition对象集合,并遍历,调用registerFeignClient方法,传入类上FeignClient注解的属性信息。

image.png

3.3、注册FeignClientFactoryBean的 BeanDefinition对象,并设置属性值。

registerFeignClient方法会 创建一个FeignClientFactoryBean的BeanDefinition对象,并把类上FeignClient注解的属性信息加到BeanDefinition的propertyValues容器。实例化FeignClientFactoryBean时,会根据 propertyValues容器对相应的属性进行初始化。

最终 注册FeignClientFactoryBean的BeanDefinition对象到Spring容器中。等待后续实例化

image.png

3.4、Feign客户端对象的获取

需要注意的是 FeignClientFactoryBean会实现FactoryBean接口,和mybatis注册mapper的代理实例到spring容器里的原理一样,会在实现的getObject()方法中返回真正的代理对象,最终注册到spring容器中的也是 getObject()方法返回的对象。

点进FeignClientFactoryBean的getObject(),

首先会先从Spring容器中获取Feign上下文FeignContext对象。

如果 @Feign没有指定 url的话,那么就是 根据服务名称来调用,走这。调用loadBalance方法。

image.png

loadBalance方法里 会先从 Feign上下文对象里获取服务名称对应的 Feign客户端对象,

image.png
image.png

getInstance方法会根据服务名 ,获取内部缓存的Spring上下文对象 ,如果没有,就新建Spring上下文对象,并缓存起来。这点和ribbon里是一样的, 每个服务名称都会 有 自己对应的 Spring上下文对象, 是相互隔离的。这里就 不再赘述了。

image.png

最后获取到Spring上下文对象,从中 获取 Feign客户端对象。

image.png

3.5、接口代理对象的生成

获取到Feign客户端对象对象之后,塞入构造器中,最后 调用Targeter.target方法

image.png

3.5.1、FallbackFactory降级工厂类实例

进入HystrixTargeter.target方法。

判断@FeignClient注解 配置降级的方式 是 Fallback属性还是FallbackFactory属性。优先Fallback属性。

我们的实例代码中配置的是FallbackFactory,调用targetWithFallbackFactory方法。

image.png

一开始会调用我们自己配置的FallbackFactory类的,create方法,返回Fallback对象。

image.png

进入我们写的FallbackFactory实现类,返回的是@FeignClient注解 的接口匿名实现类。里面重写的方法 就是 对应 远程调用方法的 降级方法。

image.png

返回出Fallback对象之后,进行判空,判断是否是 @FeignClient注解的接口 的实现类。

(这一步其实就是Feign组件在Spring容器启动时 对 我们配置的降级类 进行校验。)

然后调用构造器builder.target

image.png

设置FallbackFactory到构造器中,返回调用构造器的 build方法 构建 ReflectiveFeign对象。

image.png

3.5.2、ReflectiveFeign对象 & InvocationHandlerFactory的匿名对象的创建

这里会创建一个InvocationHandlerFactory接口的匿名实现类对象,InvocationHandlerFactory是创建HystrixInvocationHandler的工厂。 create方法体会返回HystrixInvocationHandler对象。

image.png

一系列包装最终返回ReflectiveFeign对象

image.png

3.5.3、创建代理对象

然后调用ReflectiveFeign对象的 newInstance方法,创建代理对象。

image.png

3.5.3.1、对象方法和MethodHandler映射的建立

3.5.3.1.1、方法名和MethodHandler映射的建立

一开会会调用targetToHandlersByName.apply方法,遍历接口里的方法,建立方法名和MethodHandler映射关系,存在map中。

image.png
image.png

key是接口名#方法名(参数类型列表),

value则会调用SynchronousMethodHandler.Factory factory 对象的create方法,创建SynchronousMethodHandler对象。

image.png

最终返回这个方法名和MethodHandler映射关系的map。

3.5.3.1.2、方法和MethodHandler映射的建立

拿到方法名和MethodHandler映射关系的map之后, 然后遍历接口的所有方法, 拼出 接口名#方法名(参数类型列表) 这种字符串 作为key, 从 map 获取 value :SynchronousMethodHandler对象。

然后 用method对象作为key, SynchronousMethodHandler对象作为value, 建立 方法和MethodHandler映射关系。put到methodToHandler这个map中

image.png

3.5.3.2、创建InvocationHandler对象

调用factory.create方法 , 传入接口,方法和MethodHandler映射关系map对象methodToHandler,创建JDK动态代理中的InvocationHandler对象。

image.png

factory是 刚才build方法里设置进去的InvocationHandlerFactory接口的匿名实现类对象,实现的create方法,会返回HystrixInvocationHandler对象。这里就会调到它,传入接口target,和方法和MethodHandler映射关系,还有FallbackFactory。

image.png

复制给 HystrixInvocationHandler里成员变量

image.png

注意下,dispatch属性就是 方法和MethodHandler映射关系。后面调用会用到。

最终new出 HystrixInvocationHandler对象,返回

3.5.3.3、生成动态代理对象。

接着把 HystrixInvocationHandler对象,传到Proxy.newProxyInstance方法中, 实现接口为@FeignClient注解的接口, invokeHandle对象是 HystrixInvocationHandler对象,

image.png

HystrixInvocationHandler实现InvocationHandler接口,那么 在调用 代理对象的方法时,就会调用到 HystrixInvocationHandler.invoke()方法进行 代理。

feign时调用的逻辑的就全在 HystrixInvocationHandler.invoke()方法里了。

最终这个getObject返回出 实现 有@FeignClient注解的接口 的 代理对象,注册到Spring容器中。我们从其他地方注入这个接口的实例,实际上 注入的这就这个代理对象。

4、调用时的代理逻辑

4.1、调用 hystrix组件,进行服务隔离,降级,熔断

点到 HystrixInvocationHandler.invoke方法

首先是方法判断,equals,hashCode,toString方法不 走增强,直接调原生方法

image.png

下面是具体的调用,会调用Hystrix的组件 : HystrixCommand,重写run,getFallback方法

4.1.1、run()

  1. run方法 :这个方法在Hystrix发起远程调用的时候会调到, 在里面执行业务方法。
image.png

会从HystrixInvocationHandler对象的dispatch属性里根据method对出SynchronousMethodHandler对象,调它的invoke方法。

前面讲了HystrixInvocationHandler对象的dispatch属性 是个map,存的是 method对象和 SynchronousMethodHandler对象的映射关系

4.1.2、getFallback()

  1. getFallback() : 获取降级的返回值,这个是Hystrix 远程调用降级时调用的,会执行我们定义的降级方法,返回返回值。
image.png

调用fallbackFactory.create方法 ,这个会创建到我们自己写的FallbackFactory实现类,然后返回 有@FeignClient接口的 实现类对象。

后面从映射关系中获取返回到method对象,反射调用。

下面就会调用hystrixCommand的execute方法。

image.png

还是走的 hystrix 那一套,最后会走到上面hystrixCommand对象的 run() 钩子方法中,如果需要降级的话,就走 getFallback()钩子方法。

关于hystrix后续的执行流程就不再赘述了,之前的SpringCloud之Hystrix源码 里有详细介绍。

4.2、调用ribbon组件 负载均衡

当 hystrix组件,判断 是否熔断, 线程池或者信号量是否已满等条件之后,允许接受请求的话,那么就会执行上面执行的HystrixCommand的run方法,去发起远程调用。

远程调用的话肯定是ribbon完成的,在这个方法中 就会调用ribbon组件负载均衡之后 发起对某台主机的 对应的接口请求。

image.png

上面讲过HystrixInvocationHandler的dispatch存放的是存的是 method对象和 SynchronousMethodHandler对象的映射关系,这里就会根据method对象get出 SynchronousMethodHandler对象,调用他的invoke方法。

点进SynchronousMethodHandler的invoke方法。

image.png

SynchronousMethodHandler在实例化的时候会传入Feign客户端对象,存到成员变量client中。

这个工程由于启用的链路追踪,所以这里的feign类型是TraceLoadBalancerFeignClient。如果没有链路追踪,应该就是TraceLoadBalancerFeignClient的父类:LoadBalancerFeignClient类型。TraceLoadBalancerFeignClient类相较于 LoadBalancerFeignClient类就是在ribbon调用的前后 多记录了 本地调用的 链路信息而已。

调用feign客户端的execute方法

image.png

在调用前 执行了链路追踪的相关逻辑后,走到父类LoadBalancerFeignClient的execute方法

image.png

4.2.1、获取FeignLoadBalancer实例

接下来获取调用lbClient(clientName) 方法获取LoadBalancer对象。

image.png

调用CachingSpringLoadBalancerFactory.create方法

image.png

有个缓存,第一次进来肯定缓存里没有,会调用SpringClientFactory对象获取ribbon的配置IClientConfig实例, ribbon的ILoadBalancer实例(调用的ribbon源码)。最终包装到 FeignLoadBalancer实例中。最后返回。

image.png

4.2.2、Ribbon负载均衡

获取到FeignLoadBalancer实例之后,调用executeWithLoadBalancer方法

image.png

executeWithLoadBalancer方法里会创建 LoadBalancerCommand对象,并submit 一个 ServerOperation匿名实现类对象。LoadBalancerCommand对象会调用它的call方法。

ServerOperation匿名实现类对象里会根据 负载均衡选择的 具体服务进行 http请求。

image.png

点进LoadBalancerCommand的submit则会 有 服务的选择。

image.png

最终则是调到ribbon源码里的ZoneAwareLoadBalancer对象的chooseServer方法,从服务列表里通过负载均衡算法 选则 一个具体的服务实例。

image.png

选出具体的服务之后,调用ServerOperation匿名实现类对象的call方法,传入具体的服务实例进去,对其发起http请求。

image.png
image.png

最终返回请求结果。

4.2.3、调用ribbon总结

其中获取ribbon的配置IClientConfig实例, ribbon的ILoadBalancer实例,包括后续用ILoadBalancer实例进行负载均衡(选择服务列表中的某个服务),远程调用,都是调用的ribbon的组件了。这里就不再赘述,之前的SpringCloud之Ribbon源码 里有详细介绍。

5、Feign源码总结

经过通篇源码的介绍,我们可以更清楚的了解到Feign组件在整个调用过程中的职责定位。

可以看到 Feign组件 最核心的工作还是 对我们带有@FeignClient注解的接口,生成动态代理。然后在动态代理对象的增强逻辑里,调用 hystrix 组件 进行 服务隔离,降级,熔断等功能。如果hystrix组件判断 允许接受本次请求,那么就会调用 ribbon 组件进行负载均衡,最终对 ribbon组件选择出的某个服务实例 发起http请求。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容