1、前言
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.pngFeignClientsRegistrar类,会实现ImportBeanDefinitionRegistrar接口,那么就必定会实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry)方法。在Spring容器把FeignClientsRegistrar导入,实例化,之后调用。
AnnotationMetadata就是导入他的类的元数据对象。主要是用来获取@EnableFeignClients注解里的具体配置。
然后在 registerBeanDefinitions方法中根据配置,注册Feign客户端。
3、Feign客户端的注册
看下FeignClientsRegistrar的registerBeanDefinitions方法逻辑。
会调用registerFeignClients方法,传入的AnnotationMetadata对象就是类上有 @EnableFeignClients的启动类MicroUserApplication。可以看到里面是有注解对象的。
image.png3.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.png3.2、扫描包,过滤之后返回BeanDefinition对象集合
从@EnableFeignClients注解的配置信息,确定好要扫的包路径以及过滤条件后。开始用扫描器扫包,扫描出来符合条件的类,返回BeanDefinition对象集合,并遍历,调用registerFeignClient方法,传入类上FeignClient注解的属性信息。
image.png3.3、注册FeignClientFactoryBean的 BeanDefinition对象,并设置属性值。
registerFeignClient方法会 创建一个FeignClientFactoryBean的BeanDefinition对象,并把类上FeignClient注解的属性信息加到BeanDefinition的propertyValues容器。实例化FeignClientFactoryBean时,会根据 propertyValues容器对相应的属性进行初始化。
最终 注册FeignClientFactoryBean的BeanDefinition对象到Spring容器中。等待后续实例化
image.png3.4、Feign客户端对象的获取
需要注意的是 FeignClientFactoryBean会实现FactoryBean接口,和mybatis注册mapper的代理实例到spring容器里的原理一样,会在实现的getObject()方法中返回真正的代理对象,最终注册到spring容器中的也是 getObject()方法返回的对象。
点进FeignClientFactoryBean的getObject(),
首先会先从Spring容器中获取Feign上下文FeignContext对象。
如果 @Feign没有指定 url的话,那么就是 根据服务名称来调用,走这。调用loadBalance方法。
image.pngloadBalance方法里 会先从 Feign上下文对象里获取服务名称对应的 Feign客户端对象,
image.png image.pnggetInstance方法会根据服务名 ,获取内部缓存的Spring上下文对象 ,如果没有,就新建Spring上下文对象,并缓存起来。这点和ribbon里是一样的, 每个服务名称都会 有 自己对应的 Spring上下文对象, 是相互隔离的。这里就 不再赘述了。
image.png最后获取到Spring上下文对象,从中 获取 Feign客户端对象。
image.png3.5、接口代理对象的生成
获取到Feign客户端对象对象之后,塞入构造器中,最后 调用Targeter.target方法
image.png3.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.png3.5.2、ReflectiveFeign对象 & InvocationHandlerFactory的匿名对象的创建
这里会创建一个InvocationHandlerFactory接口的匿名实现类对象,InvocationHandlerFactory是创建HystrixInvocationHandler的工厂。 create方法体会返回HystrixInvocationHandler对象。
image.png一系列包装最终返回ReflectiveFeign对象
image.png3.5.3、创建代理对象
然后调用ReflectiveFeign对象的 newInstance方法,创建代理对象。
image.png3.5.3.1、对象方法和MethodHandler映射的建立
3.5.3.1.1、方法名和MethodHandler映射的建立
一开会会调用targetToHandlersByName.apply方法,遍历接口里的方法,建立方法名和MethodHandler映射关系,存在map中。
image.png image.pngkey是接口名#方法名(参数类型列表),
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.png3.5.3.2、创建InvocationHandler对象
调用factory.create方法 , 传入接口,方法和MethodHandler映射关系map对象methodToHandler,创建JDK动态代理中的InvocationHandler对象。
image.pngfactory是 刚才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.pngHystrixInvocationHandler实现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()
- run方法 :这个方法在Hystrix发起远程调用的时候会调到, 在里面执行业务方法。
会从HystrixInvocationHandler对象的dispatch属性里根据method对出SynchronousMethodHandler对象,调它的invoke方法。
前面讲了HystrixInvocationHandler对象的dispatch属性 是个map,存的是 method对象和 SynchronousMethodHandler对象的映射关系
4.1.2、getFallback()
- getFallback() : 获取降级的返回值,这个是Hystrix 远程调用降级时调用的,会执行我们定义的降级方法,返回返回值。
调用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.pngSynchronousMethodHandler在实例化的时候会传入Feign客户端对象,存到成员变量client中。
这个工程由于启用的链路追踪,所以这里的feign类型是TraceLoadBalancerFeignClient。如果没有链路追踪,应该就是TraceLoadBalancerFeignClient的父类:LoadBalancerFeignClient类型。TraceLoadBalancerFeignClient类相较于 LoadBalancerFeignClient类就是在ribbon调用的前后 多记录了 本地调用的 链路信息而已。
调用feign客户端的execute方法
image.png在调用前 执行了链路追踪的相关逻辑后,走到父类LoadBalancerFeignClient的execute方法
image.png4.2.1、获取FeignLoadBalancer实例
接下来获取调用lbClient(clientName) 方法获取LoadBalancer对象。
image.png调用CachingSpringLoadBalancerFactory.create方法
image.png有个缓存,第一次进来肯定缓存里没有,会调用SpringClientFactory对象获取ribbon的配置IClientConfig实例, ribbon的ILoadBalancer实例(调用的ribbon源码)。最终包装到 FeignLoadBalancer实例中。最后返回。
image.png4.2.2、Ribbon负载均衡
获取到FeignLoadBalancer实例之后,调用executeWithLoadBalancer方法
image.pngexecuteWithLoadBalancer方法里会创建 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请求。
网友评论