openFeign应用和源码分析

以下内容从两个方面入手,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源码篇楼主已经做出了分析,不懂得小伙伴可以移步去看看。到这里调用的过程已经完成了。

总结:大家看源码的时候估计也一定觉得很晕很绕,楼主建议以下大家看源码的时候首先明确看源码的主流程,而不是每一处都是断点进去调试,如果你每处都进行断点调试会被绕着越来越懵比。接着就是看之前 要有自己的猜想猜测,就是带着目的去验证这个东西不是不是这样子的。这样看起来感觉比较有成就感了。。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值