暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring Cloud Feign【源码篇】Feign 如何进行服务间请求调用

花好夜猿 2019-06-23
709

参考资料:Spring Cloud 官网
https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#_spring_cloud_openfeign

相关版本:Spring Boot 2.1.5 、 spring cloud Greenwich.SR1 
spring-cloud-openfeign 2.1.1

Spring Cloud Feign 默认使用 HTTP 的形式进行远程服务调用。

先来看一个简单的案例,假设此时有一个服务provider 和一个服务consumer。




服务provider9527提供接口/hello/{name}
服务consumer9528调用 服务provider的 hello/{name} 接口

服务consumer 通过 RestTemplate 发起 HTTP 请求调用服务 provider 接口代码如下:

在上面代码中,我们通过传入一个字符参数,发起 http 请求,接收一个字符串的返回结果。

假设,此时我们需要传入的是一个对象,对象中存在多个参数,返回结果同样也是一个对象。
这时候,为了能够提高开发效率,我们就需要对 RestTemplate 进行封装,例如,通过反射获取参数对象值,并进行拼接,返回结果同样需要通过反射进行赋值等。

而 Feign 帮我们对 RestTemplate 进行了封装,除此之外,Feign 集成了 Hystrix ,Ribbon 等功能。在之后会针对 Feign 对 Hystrix 和 Ribbon 等集成进行分析。

Feign 简单例子

step1、引入依赖

step2、开启 Feign 功能
@EnableFeignClients

step3、编写相关代码,如下图

总结:使用 Feign 进行远程调用,只需定义一个 Feign 客户端,通过调用方法的形式,便可完成远程接口调用。

Feign Client 如何被加载到 Spring 中

在上面 Feign 调用的案例中 HelloClient 只需 @FeignClient 注解,然后 @Autowired 进行注入便可完成 Feign Client 注入。

下面通过源码分析,看看 Feign Client 是如何被加载到 Spring 中的。

Feign Client BeanDefinition 信息注

@EnableFeignClients 为入口,进行源码分析

通过 EnableFeignClients 源码查看,聚焦 @Import(FeignClientsRegistrar.class) 中的 FeignClientsRegistrar.class

FeignClientsRegistrar

FeignClientsRegistrar 类实现了 ImportBeanDefinitionRegistrar 。聚焦 FeignClientsRegistrar#registerBeanDefinitions 方法。

知识点:实现了 ImportBeanDefinitionRegistrar 的类,会根据该类的配置属性进行相关 Bean 的动态注入。

FeignClientsRegistrar#registerBeanDefinitions

方法逻辑就两个方法

  • FeignClientsRegistrar#registerDefaultConfiguration
    该方法主要是获取注解 @EnableFeignClients 中是否有相关的配置信息,如果有进行配置信息的注册

  • FeignClientsRegistrar#registerFeignClients
    该方法是用来将我们定义的带有 @FeignClient 注解的类注册到 BeanDefinitionRegistry 中

追踪主线方法 FeignClientsRegistrar#registerFeignClients

FeignClientsRegistra#registerFeignClients

主要处理逻辑

1、获取所有需要扫描的包路径。
该路径可以从 EnableFeignClients 中的配置中获取,如:value,basePackages,basePackageClasses。
如果 EnableFeignClients 中没有进行配置,默认扫描包路径为:应用 @EnableFeignClients 的类,也就是 Application 启动类所在包路径为主。
例如:com.qguofeng.Application.class 启动类 中 @EnableFeignClients 中没有相关扫描包配置时,则默认扫描 FeignClient 的路径为:com.qguofeng

2、增加扫描过滤条件
在 步骤 1 中确定了需要扫描的包路径,为了获取精确数据,增加筛选条件: 含有 FeignClient.class 注解的类

3、根据 步骤1 确定的 扫描路径,步骤二确定的过滤条件,获取含有注解 FeignClient 的所有的相关类的 Set 集合

4、遍历步骤三获取到的 set 集合,如果 FeignClient 相关类注解 @FeignClient 中含有相关的配置类,则进行配置的注入。然后进行类的注入。

我们来看看执行 Feign Client 注册的时候,注册了哪些信息。

FeignClientsRegistra#registerFeignClient

总结
@EnableFeignClients 为入口,我们能够得知,在 FeignClientsRegistrar 中,通过扫描相关包路径下的包含注解 @FeignClient 的 Feign Client 的 BeanDefinition 注册到 Spring 中。

通过上面的分析,我们知道了 Feign Client 类相关信息 BeanDefinition 被注册到了 Spring 中,此时 Feign Client 并没有被实例化,Feign Client 真正进行实例化是在 @Autowired 时,才进行的实例化。

Feign Client 实例化

在之前文章中我们提到得依赖注入:IOC/DI  ,Spring 在进行依赖注入时会有进行 bean 的实例化操作。

知识点回顾: AbstractAutowireCapableBeanFactory

AbstractAutowireCapableBeanFactory#doCreateBean 方法开始,进行实例化。
实例化调用路径方法
AbstractAutowireCapableBeanFactory#createBeanInstance ->
AbstractAutowireCapableBeanFactory#obtainFromSupplier-> 
AbstractAutowireCapableBeanFactory#getObjectForBeanInstance-> 
AbstractBeanFactory#getObjectForBeanInstance-> 
FactoryBeanRegistrySupport#getObjectFromFactoryBean-> 
FactoryBeanRegistrySupport#getObject() 。

最调用 org.springframework.beans.factory.FactoryBean#getObject() 获取到实例对象。

Spring 中 Bean 的获取通常都是需要从相对应的工厂类中进行获取,Feign 也不例外。Feign 实例对象获取的工厂类为 FeignClientFactoryBean 。

查看 FeignClientFactoryBean  如何生成实例化对象


聚焦 FeignClientFactoryBean#getObject()

FeignClientFactoryBean#getTarget() 实例化对象操作中,会有两种形式的方法调用:

  • return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));

  • return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));

这两种返回调用最终都是调用的 Targeter#targeter.target(……) 方法。

通过上面的代码

FeignContext context = this.applicationContext.getBean(FeignContext.class);
Targeter targeter = get(context, Targeter.class);

会发现 Targeter 对象直接是从 Spring 上下文中获取到的。

那么究竟 Targeter 是在什么时候加载到 Spring 上下文中的?

通过查看 Targeter 的两个实现类:DefaultTargeter HystrixTargeter

我们可以聚焦到 Feign 自动配置类 FeignAutoConfiguration

友情链接:Spring Boot 自动配置

FeignAutoConfiguration 存在Targeter 的两个实现类: DefaultTargeter 和 HystrixTargeter 的自动配置Bean。

  • DefaultTargeter 如果依赖中没有 Hystrix ,以 DefaultTargeter 进行自动配置

  • HystrixTargeter 如果依赖中有 Hystrix ,以 HystrixTargeter 进行自动配置


上面我们说到Feign Client 最终调用的是 Targeter#targeter.target(……) 方法。这里以 DefaultTargeter 进行展开。

DefaultTargeter#target() 方法调用的 Feign.Builder target() 方法。

聚焦 Feign.Builder 的 target()

Feign.Builder 的 target() 方法转而调用 ReflectiveFeign#newInstance()

聚焦ReflectiveFeign#newInstance

这里主线代码很直观,对 HardCodedTarget<>(this.type, this.name, url) 进行代理,生成一个新的代理对象。

总结

@Autowired 为入口,加上 Feign 的自动配置 FeignAutoConfiguration 。我们得到:Feign Client 在依赖注入时,对 Feign Client 进行实例化操作。实例化对象从 FeignClientFactoryBean 工厂中获取,获取到 HardCodedTarget<>(this.type, this.name, url) 该实例对象被 ReflectiveFeign.FeignInvocationHandler 代理。

Feign Client 如何执行调用

在上面的流程中,Feign Client 生成了一个 被 ReflectiveFeign.FeignInvocationHandler 代理的 HardCodedTarget 对象,对象信息如下

所以在使用 Feign Client 进行调用的时候,方法会进入到 ReflectiveFeign.FeignInvocationHandler 中。

ReflectiveFeign.FeignInvocationHandler 是一个 JDK 动态代理类。

聚焦 ReflectiveFeign.FeignInvocationHandler#invoke 方法

dispatch.get(method) 拿到的是一个 SynchronousMethodHandler 。其中包含了 Feign Client 被代理的对象 HardCodedTarget

聚焦 SynchronousMethodHandler#invoke


业务逻辑:将请求参数和请求路径封装成 RequestTemplate ,然后调用 SynchronousMethodHandler#executeAndDecode()方法执行

封装之后 RequestTemplate 信息如下:

聚焦SynchronousMethodHandler#executeAndDecode

1、拼接完整的 request请求方法 targetRequest(template); 最终会调用 HardCodedTarget#apply() 。HardCodedTarget#apply() 组合封装了完整的 request请求。request请求信息如下:

上面就是 Feign Client 调用远程服务,并获取到响应数据的主线流程。复杂逻辑处理这里不进行展开。

总结

Feign Client 进行服务间调用分为几个步骤:

1、Spring 根据 扫描包中 使用了注解 @FeignClient 的 Feign Client 。将 Feign Client 信息抽象成为 BeanDefinition 并注册到 Spring 中。
2、在使用 @Autowired 注入 Feign Client 时,触发依赖注入,根据 Feign Client 的 BeanDefinition 信息,实例化 Feign Client ,并生成代理对象, 并放入 IOC 容器中。
3、执行调用 Feign Client 的代理对象 ReflectiveFeign.FeignInvocationHandler 的 invoke() 方法。该方法经过一系列调用拼装 RequestTemplate 请求对象,并发送请求调用,获取响应。


-- END --

       长按二维码      

关注 [ WTF名字好难取 ] 公众号


往期回顾


Spring Cloud Hystrix【应用篇】 Hystrix Dashboard

Spring Cloud Hystrix【应用篇】

Spring Cloud Hystrix【理论篇】

Nginx【应用篇】记一次Nginx问题 SSL_SHUTDOWN

日志【基础篇】带你捋清楚Java混乱的日志体系

文章转载自花好夜猿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论