美文网首页
19. SpringCloud之Feign源码解析

19. SpringCloud之Feign源码解析

作者: 天还下着毛毛雨 | 来源:发表于2022-05-01 15:19 被阅读0次
    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请求。

    相关文章

      网友评论

          本文标题:19. SpringCloud之Feign源码解析

          本文链接:https://www.haomeiwen.com/subject/umkpyrtx.html