目录

Feign的说明

简单集成方式

调用方式

OpenFeign调用原理

OpenFeign 包扫描原理

注册 FeignClient 到 Spring 的原理

OpenFeign 动态代理原理

对SpringMVC注解的解析

OpenFeign 发送请求的原理

FeignClientFactoryBean做了哪些事?

ReflectiveFeign做了哪些事?

为OpenFeign增加简单负载均衡


Feign的说明

Feign的英文释义:假装,装作,佯装

Feign 作用是来简化我们发起远程调用的代码的,简化成像调用本地方法那样。

Feign 和 OpenFeign 有很多大同小异之处,不同的是 OpenFeign 支持 MVC 注解

简单总结下 OpenFeign 能用来做什么:

  1. OpenFeign 是声明式的 HTTP 客户端,让远程调用更简单。
  2. 提供了HTTP请求的模板(RestTemplate),编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息
  3. 可以整合负载均衡组件(RibbonLoadBalancer)和 服务熔断(HystixSentinel),不需要显示调用
  4. Spring Cloud Feign 在 Netflix Feign的基础上扩展了对SpringMVC注解的支持

简单集成方式

springboot feignclient 参数为空 springboot open feign_springboot

Pom加入依赖

springboot feignclient 参数为空 springboot open feign_云计算_02

应用程序加入注解

springboot feignclient 参数为空 springboot open feign_java_03

配置接口

springboot feignclient 参数为空 springboot open feign_rpc_04

配置文件增加调用地址

springboot feignclient 参数为空 springboot open feign_spring cloud_05

使用远程接口

springboot feignclient 参数为空 springboot open feign_rpc_06

调用方式

整体调用时序图

springboot feignclient 参数为空 springboot open feign_springboot_07

OpenFeign调用原理

初始化过程

springboot feignclient 参数为空 springboot open feign_rpc_08

调用过程

springboot feignclient 参数为空 springboot open feign_java_09

关键对象:

FeignClientFactoryBean.getObject,用于对接Spring,关联@FeignClient注解

ReflectiveFeign.newInstance      生成代理对象

HardCodedTarget               存储接口信息以及FeignClient信息

Client接口                    远程调用执行工具

OpenFeign 包扫描原理

springboot feignclient 参数为空 springboot open feign_rpc_10

1) 声明Feign调用接口时,不用加@Component 注解,因为在Application那里已经加了EnableFeignClients。在Application上加了EnableFeignClients注解,会使用 Spring 框架的 Import 注解导入FeignClientsRegistrar 类,自动检查@FeignClient注解

springboot feignclient 参数为空 springboot open feign_java_11

springboot feignclient 参数为空 springboot open feign_云计算_12

2) FeignClientsRegistrar 负责 Feign 接口的加载。

由于FeignClientsRegistrar 实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,所以,可以由ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry主动调起

springboot feignclient 参数为空 springboot open feign_java_13

3) registerFeignClients 会对指定路径执行包扫描

springboot feignclient 参数为空 springboot open feign_springboot_14

通过getBasePackages方法,从metadata里获取EnableFeignClients注解里的包扫描参数,存入basePackages集合中,可能是多个。通过路径扫描,在basePackage路径下,找到所有的备选组件将所有备选组件存入candidateComponents集合

springboot feignclient 参数为空 springboot open feign_云计算_15

4) 只保留带有 @FeignClient 的接口。

springboot feignclient 参数为空 springboot open feign_云计算_16

5) 循环将所有@FeignClient注释的接口注册到Spring中

springboot feignclient 参数为空 springboot open feign_java_17

代码示例:

springboot feignclient 参数为空 springboot open feign_云计算_18

注册 FeignClient 到 Spring 的原理

springboot feignclient 参数为空 springboot open feign_云计算_19

还是在 registerFeignClients 方法中,当 FeignClient 扫描完后,就要为这些 FeignClient 接口生成一个动态代理对象。

registerFeignClient调用eagerlyRegisterFeignClientBeanDefinition方法注册接口

首先,生成一个基于FeignClientFactoryBean的Bean定义

springboot feignclient 参数为空 springboot open feign_java_20

springboot feignclient 参数为空 springboot open feign_spring cloud_21

根据类的名字我们可以知道这是一个工厂Bean,用来创建 FeignClient Bean 的。

我们最开始用的 @FeignClient,里面有个参数 "remoteSysService",这个是注解的属性,当 OpenFeign 框架去创建 FeignClient Bean 的时候,就会使用这些参数去生成 Bean。

springboot feignclient 参数为空 springboot open feign_java_22

RegisterFeignClientBeanDefinition的注册步骤如下:

1)创建一个class是FeignClientFactoryBean的beanDefinition 对象;

2)解析@FeignClient注解定义接口的各个属性,将各个属性设置到beanDefinition 中。如url、path、name、contextId(就是我们定义的远程服务名称)、fallbackFactory等;

3)然后将 beanDefinition 转换成一个 BeanDefinitionHolder,这个 holder 就是包含了 beanDefinition, alias, beanName 信息。

4)最后将这个 holder 注册到 Spring 容器中。

springboot feignclient 参数为空 springboot open feign_spring cloud_23

Registry就是DefaultListableBeanFactory,他将RemoteSystemService注册到容器中,

springboot feignclient 参数为空 springboot open feign_java_24

需要注意的是beanDefinition里存的class不是RemoteSystemService,而是FeignClientFactoryBean,他做为一个工厂Bean为后续调用提供Bean的创建。

springboot feignclient 参数为空 springboot open feign_java_25

至此,FeignClient注册的初始化已经完成。

小节:注册到Spring容器之后,服务要调用接口的时候,就可以直接用 FeignClient 的接口方法了

springboot feignclient 参数为空 springboot open feign_springboot_26

返回结果如果使用集成HashMap的方式,可以在不定义Provider的实体对象即可以使用。

springboot feignclient 参数为空 springboot open feign_java_27

那么这个服务以及方法是如何被调起的呢?

OpenFeign 动态代理原理

FeignClientFactoryBean实现了FactoryBean<Object>接口

springboot feignclient 参数为空 springboot open feign_云计算_28

当RemoteSystemService被创建时,会调用FeignClientFactoryBean来创建。

springboot feignclient 参数为空 springboot open feign_云计算_29

FeignClientFactoryBean.getTarget创建出Feign.Builder

Feign.Builder.build创建出SynchronousMethodHandler.Factory(用于创建反射方法)

Feign.Builder.build创建出Client.Default(使用Java原生HttpURLConnection调用),Client是用来做远程调用的连接对象,如果pom加载OkHttp,也可以被OkHttp实现。

springboot feignclient 参数为空 springboot open feign_spring cloud_30

获取org.springframework.cloud.openfeign.DefaultTargeter(Target接口只能找到一个实现类)Targeter是用来做代理的中间对象。

通过resolveTarget方法获取HardCodedTarget<RemoteSystemService>

HardCodedTarget存取实际的调用信息

springboot feignclient 参数为空 springboot open feign_springboot_31

获取Target实现类DefaultTargeter通过Feign.Builder创建出ReflectiveFeign,

springboot feignclient 参数为空 springboot open feign_rpc_32

这段有点复杂,实际调用链是:FeignClientFactoryBean中的targeter.target  -->  DefaultTargeter中的feign.target  -->  Feign.target  -->  Feign.build()创建ReflectiveFeign  -->  ReflectiveFeign.newInstance创建出实际代理类。

ReflectiveFeign通过newInstance方法创建代理getTenantByTenantId(java.lang.Long),

target作用是存储远程调用信息,实际是HardCodedTarget对象。

springboot feignclient 参数为空 springboot open feign_springboot_33

最终返回了一个对getTenantByTenantId(java.lang.Long)的代理

springboot feignclient 参数为空 springboot open feign_java_34

FeignClient代理模式,使用FeignInvocationHandler持有一个对FeignClient注解接口的代理(Proxy),将注解接口中的MVC方法解析出来(例如GetMapping注解的方法),将接口包含的所有MVC方法存放到一个HashMap(dispatch)中,为后续提供调用。

对SpringMVC注解解析

ReflectiveFeign.newInstance中,对Map<Method, MethodHandler> methodToHandler = targetToHandlersByName.apply(target, requestContext)的说明。通过使用OpenFeign.support.SpringMvcContract#parseAndValidateMetadata方法,可以从Controller方法的@GetMapping中解析出MethodMetadata,然后使用这个MethodMetadataton通过createMethodHandler方法来创建MethodHandler。

在ReflectiveFeign的ParseHandlersByName中的apply方法中做出解析。

springboot feignclient 参数为空 springboot open feign_云计算_35

MethodMetadata中的对@GetMapping注解内容的解析:

springboot feignclient 参数为空 springboot open feign_springboot_36

OpenFeign 发送请求的原理

springboot feignclient 参数为空 springboot open feign_spring cloud_37

ReflectiveFeign.invoke中,通过Method对象,获取到SynchronousMethodHandler对象

最终调用的是SynchronousMethodHandler的invoke方法。

springboot feignclient 参数为空 springboot open feign_rpc_38

然后在SynchronousMethodHandler中,executeAndDecode方法调用了Client的execute

springboot feignclient 参数为空 springboot open feign_springboot_39

本次的Client是由Feign.Builder默认创建出来的

springboot feignclient 参数为空 springboot open feign_springboot_40

通过convertAndSend调用Java原生的HttpURLConnection对象实现Http请求

java.net.HttpURLConnection是抽象类

sun.net.www.protocol.http.HttpURLConnection是实现类

springboot feignclient 参数为空 springboot open feign_rpc_41

另外,如果增加okhttp依赖的话,就会使用okhttp作为客户端,来代替Client.Default(创建原生HttpURLConnection)。

springboot feignclient 参数为空 springboot open feign_java_42

Okhttp会使用连接池的方式来管理连接

springboot feignclient 参数为空 springboot open feign_云计算_43

这里就不在使用Client.Default创建连接,而是使用OkHttpClient了

springboot feignclient 参数为空 springboot open feign_java_44

FeignClientFactoryBean做了哪些事?

通过getObject创建FeignClient客户端

获取FeignClientFactory工厂

创建Feign.Builder

根据Client接口的实现情况,创建Client接口对应的实现,默认选择Client.Default

根据Targeter接口的实现情况,创建Targeter接口对应的实现,默认选择DefaultTargeter

根据Target接口的实现情况,创建Target接口对应的实现,默认选择Target.HardCodedTarget

在有URL的情况下,创建Proxy代理

在没有配置URL的情况下,查找负载均衡的Feign代理

ReflectiveFeign做了哪些事?

对Feign接口进行代理

解析target(FeignClient注解的方法)对象,生成MethodMetadata

根据MethodMetadata生成MethodHandler集合

根据MethodHandler集合生成InvocationHandler

根据InvocationHandler生成Proxy对象

将MethodHandler集合绑定到Proxy

实现一个InvocationHandler接口默认的FeignInvocationHandler

在调用Feign接口时,承担invoke的入口,将MethodHandler调起

为OpenFeign增加简单负载均衡

初始化过程

FeignClientFactoryBean.getTarget

springboot feignclient 参数为空 springboot open feign_springboot_45

springboot feignclient 参数为空 springboot open feign_spring cloud_46

springboot feignclient 参数为空 springboot open feign_java_47

springboot feignclient 参数为空 springboot open feign_云计算_48

调用过程

FeignBlockingLoadBalancerClient.execute调用BlockingLoadBalancerClient.choose选择服务器

springboot feignclient 参数为空 springboot open feign_spring cloud_49

springboot feignclient 参数为空 springboot open feign_rpc_50

springboot feignclient 参数为空 springboot open feign_云计算_51

springboot feignclient 参数为空 springboot open feign_spring cloud_52

springboot feignclient 参数为空 springboot open feign_spring cloud_53

也可以结合Nacos创建功能更强大的负载均衡。