以下内容从两个方面入手,openFegin的应用和原理分析
一、openFegin 的应用
举例场景 :会员需要调用订单服务拿到订单信息,所以产生的微服务有订单服务、会员服务,这里就简单的定义 订单服务是提供者服务,会员调用订单服务
1. 项目结构如下:
(1). 订单服务里面有 order-api 和 order-service,api是提供给 user-service调用;order-service是是具体的服务实现和进行独立部署。
(2). 订单服务order-api 下面的结构,clients 存放对外的feignClient,dto存放 restful 调用的对象模型
这种结构的定义,如果小伙伴们用过dubbo的就会比较熟悉
2. openFegin 使用
以下 (1) (2) 点是 在order-api 声明定义 ,对此api的声明已经完成了;
第 (3) 点主要是继承 api 进行服务实现;
第(4)(5)(6)点,主要是怎么进行调用api的服务
(1)定义服务接口
(2)@FeignClient 注解使用
(3)定义接口的实现(order-service实现)
这里的用法就很熟悉了,就类似springMVC, 对服务的实现类进行声明为 @RestController,具体的方法路径已经在公共的api上已经声明好了,在这里就不需要声明了
(4)消费者引用 api 依赖(user依赖 order-api)
(5)@EnableFeignClients(basePackages = "com.gupaoedu.example.clients")
basePackages 的扫描包是api的包,不然容器 扫描加载不了 api 加载 @FeignClient等配置
(6)消费者调用
二、源码分析
源码分析的验证主要关注点:
(1)openFeign的注入怎么生成动态代理对象的?
(2)openFeign怎么进行上下文隔离的? 就例如有声明多个@FeignClient 怎么进行隔离
(3)openFeign调用怎么进行集成用ribbon的负载均衡?
1. 回顾一下 bean 装载的两种方式
. ImportSelector
. ImportBeanDefifinitionRegistrar
(1) 自定义 ImportSelector 的实现
自定义 MyImportSelector 实现 ImportSelector,自定义扫描实例类的全路径
在配置类或则Application类上导入自定义的 MyImportSelector
运行测试用例的结果,已经成功加载到IOC容器上,所以 想自定义 ImportSelector 的实现扫描类进行载入IOC容器直接实现ImportSelector类,然后声明写入扫描的类路径即可
(2) 自定义 ImportBeanDefifinitionRegistrar的实现
自定义 registrar 类 继承 ImportBeanDefinitionRegistrar
自定义注解类 import 自定义的 Registrar类
在application 上加入自定义的注解 @EnableGpRegistrara
启动的结果可以看到,已经成功加入了IOC容器,如果想自定义实现,则按照上述进行实现即可
2. @EnableFeignClients 注解动态载入IOC容器过程
点击进入EnableFeignClients 注解可以看到,有import FeginClientsRegistrar 类进行载入IOC。FeignClientsRegistrar实现了ImportBeanDefifinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefifinitions来实现动态Bean的装载。它的作用类似于ImportSelector,和上面回顾的自定义实现是一致的。
进入 FeignClientRegistrar 看到有 registerBeanDefinitions 中的 registerFeignClients(metadata,registry)进行注册bean。
registerDefaultConfifiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefifinition , 最终通过调用 Spring 框架中的BeanDefifinitionReaderUtils.resgisterBeanDefifinition 将解析处理过的 FeignClient
BeanDeififinition 添加到 spring 容器中.
registerClientConfiguration 方法主要是做FeginClientSpecification的实例
这里面需要重点分析的就是 registerFeignClients 方法,这个方法主要是扫描类路径下所有的
@FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。
继续进入 registerFeignClient 可以看到spring 熟悉的身影 factoryBean这种特殊的bean工厂。FeignClientFactoryBean 可以进行创建各种复杂的bean,例如代理生成的bean。也就是说,FeignClient被动态注册成了一个FactoryBean。
FeignClientFactoryBean 可以通过 getTarget() 获取实例,依赖注入DI 就是通过getTarget()进行获取对应的实例。简单点说FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象
这里进行百科一下:
Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会
把所有的FeignClient的BeanDefifinition设置为FeignClientFactoryBean类型,而
FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。
在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。
工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普
通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实
例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例
3. FeignClientFactoryBean.getObject 的分析
上述的第二点讲到的FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象,以下就是讲解怎么进行获取代理对象的。
1)首先进入 FeignClientFactoryBean 找到对应的 getObect()
2)进入 getObject() , 可以看到 getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。隔离上下文可以看到下面类图的属性contexts 用的是Map进行隔离,类似 ribbon 做负载均衡时用 serviceId进行隔离实例列表也是用到Contexts。
而FeignContext的实例,我们通过FeignAutoConfifiguration 可以看到在初始化FeignContext时,会把confifigurations在容器中放入FeignContext中。confifigurations的来源就是在前面 registerFeignClients 方法中将@FeignClient的配置confifiguration
FeignClientFactoryBean.getTarget:
FeignAutoConfifiguration:
类图:
3)接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
FeignContext会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器
配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设
置。实际上他们最终调用的是Target.target()方法。
feign调用:
LoadBalance:
4)针对 通过 getOptional(context, Client.class) 生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端,从上下文中获取一个Client,默认是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfifiguration这个自动装配类中,通过Import实现的
FeignRibbonClientAutoConfifiguration:
getOptional(context, Client.class) 调用过程:
在这里可以看到是通过 定义注解的名称(例如:order-service)进行 下上文隔离,如果查不到就进行创建 context, 数据结构 在 第3小节 第2)小点可以看到。看到这里的源码,小伙伴们可以进行借鉴以下这种缓存方式
5)接着 targeter.target()的调用,又进入了咱们熟悉的环节类的选择,如果集成并且用熔断器的话 就进行HystrixTargeter,在这里楼主用的是默认的DefaultTargeter
6)ReflflectiveFeign.newInstance
这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规
则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。
从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接
口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的
InvocationHandler。
通过 DefaultTargeter 一直进入,过程如下图
点进build 可以看到
7)this.targetToHandlersByName.apply(target) 接口定义的参数解析
根据Contract协议规则,解析接口类的注解信息,解析成内部表现:
targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配
置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个
的map,放入InvocationHandler的实现FeignInvocationHandler中。
这里分析以下this.targetToHandlersByName.apply(target) 的实现
进入apply方法 可以看到this.contract.parseAndValidateMetadata(target.type())进行元数据解析,SpringMVCContract就是对类方法进行解析,
继续看 this.factory.create(target, methodToHandler) 传入的是 刚才解析出来的 methodToHandler作为参数,其实传进去就是
FeignInvocationHandler 拦截类的dispatch 分发器的值
在这里我们看到了无比熟悉的代码,就是JDK动态代理,就这印证了DI 注入的是代理类的原因了。InvocationHandler 是代理后进行拦截处理类,实现类是FeignInvocationHandler
调用验证:
8)OpenFeign 调用过程,代理类进入#ReflflectiveFeign.FeignInvocationHandler.invoke
this.dispatch 分发器,类似SpringMVC 上的 handlerMapping,存储请求路径和方法等信息
而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。 this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。
这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版
9)代理拦截之后,动态生成Request
executeAndDecode:
经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个 executeAndDecode() 方
法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取
响应信息。
进入this.client.execute(request, options)的调用
在这里可以看到实现了 ribbon负载均衡的调用,这里就不做过多解析了,因为在 ribbon源码篇楼主已经做出了分析,不懂得小伙伴可以移步去看看。到这里调用的过程已经完成了。
总结:大家看源码的时候估计也一定觉得很晕很绕,楼主建议以下大家看源码的时候首先明确看源码的主流程,而不是每一处都是断点进去调试,如果你每处都进行断点调试会被绕着越来越懵比。接着就是看之前 要有自己的猜想猜测,就是带着目的去验证这个东西不是不是这样子的。这样看起来感觉比较有成就感了。。