上接:

SpringCloud OpenFeign_钱多多_qdd的博客-CSDN博客

前言

源码图:

一、Bean的动态装载

在spring系列里bean的动态装载是很常见的,感兴趣可以看看我的这篇文章:

Bean的动态装载_钱多多_qdd的博客-CSDN博客_动态加载bean

二、@FeignClient解析

从下面这个类开始切入,这个注解开启了FeignClient的解析过程。

@EnableFeignClients(basePackages = "com.gupaoedu.example.clients")

2.1 FeignClientsRegistrar

  • registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册
  • registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的
    BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient
    BeanDeifinition 添加到 spring 容器中
@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。registerDefaultConfiguration(metadata, registry);//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。registerFeignClients(metadata, registry);}

这里面需要重点分析的就是 registerFeignClients 方法,这个方法主要是扫描类路径下所有的
@FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。

由上图我们可以看到此方法大概有5个步骤:

  1. 获取@EnableFeignClient的相关属性;
  2. 获取服务端类所在的路径basePackages(@EnableFeignClient("xx.xx.xx")配置的)。后续会遍历这些路径,并找到@FeignClient注解的类,将相关的类注册;
  3. 扫描basePackage下的所有类;
  4. 获取@FeignClient注解的相关属性;
  5. 注册;

下面我们来着重看第5步注册:

private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);definition.addPropertyValue("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);definition.addPropertyValue("name", name);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);// has a default, won't be nullboolean primary = (Boolean) attributes.get("primary");beanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。

我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过
genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。
我们可以发现,FeignClient被动态注册成了一个FactoryBean,之前听过tom老师的源码的同学应该知道什么是FactoryBean吧。

Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会、把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而
FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。
在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。
工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。

public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());builder.beanDefinition.setBeanClass(beanClass);return builder;
}

简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。

2.2 FeignClientFactoryBean.getObject

getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。

FeignContext注册到容器是在FeignAutoConfiguration上完成的:

在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。

  1. 接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
  2. FeignContext在上篇中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。
  3. 实际上他们最终调用的是Target.target()方法。

2.3 loadBalance

生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端Client client = (Client)this.getOptional(context, Client.class);从上下文中获取一个Client,默认是LoadBalancerFeignClient。

——绿色②代码。

它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的:

绿色①代码:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {Client client = getOptional(context, Client.class);if (client != null) {builder.client(client);Targeter targeter = get(context, Targeter.class);return targeter.target(this, builder, context, target);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");}

2.4 DefaultTarget.target

 

2.5 ReflectiveFeign.newInstance

这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。

从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。

三、接口定义的参数解析

根据FeignClient接口的描述解析出对应的请求数据。

3.1 targetToHandlersByName.apply(target)

还是在ReflectiveFeign.newInstance方法里:

根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。

3.2 SpringMvcContract

当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务。

扩展:Feign详解4-Contract 源码_zhangyingchengqi的博客-CSDN博客

四、OpenFeign调用过程

在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。

那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是一个动态代理的实现。

而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。
this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。

这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下:

4.1 动态生成Request

4.2 executeAndDecode

经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。
 

 Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {//转化为Http请求报文Request request = targetRequest(template);if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {//发起远程通信response = client.execute(request, options);// ensure the request is set. TODO: remove in Feign 12//获取返回结果response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);}long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);if (decoder != null)return decoder.decode(response, metadata.returnType());CompletableFuture<Object> resultFuture = new CompletableFuture<>();asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,metadata.returnType(),elapsedTime);try {if (!resultFuture.isDone())throw new IllegalStateException("Response handling not done");return resultFuture.join();} catch (CompletionException e) {Throwable cause = e.getCause();if (cause != null)throw cause;throw e;}}

4.3 Client.execute

默认采用JDK的 HttpURLConnection 发起远程调用。

五、拓展

Feign源码解析 - 简书

SpringCloud OpenFeign(二)相关推荐

  1. SpringCloud OpenFeign + Nacos正确打开方式!

    作者 | 磊哥 来源 | Java中文社群(ID:javacn666) 转载请联系授权(微信ID:GG_Stone) Nacos 支持两种 HTTP 服务请求,一个是 REST Template,另一 ...

  2. SpringCloud 生成二维码技术

    文章目录 SpringCloud 生成二维码技术 1)引入依赖 2)编写生成二维码方法 3)测试方法 SpringCloud 生成二维码技术 ZXing是一个开源的,用Java编写的多格式的1D / ...

  3. SpringCloud OpenFeign 远程HTTP服务调用用法与原理

    在 openFeign 未出现前,Spring 提供了 RestTemplate 作为远程服务调用的客户端,提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率.由于文章内容会使 ...

  4. SpringCloud OpenFeign服务调用客户端介绍及配置使用

    一.OpenFeign介绍 OpenFeign是Netfix开发的一款声明式,模板化的Http服务调用客户端.使用在服务调用者工程端.OpenFeign的负载均衡也为客户端负载均衡.一下简称Feign ...

  5. SpringCloud教程二

    56_Hystrix之全局服务降级DefaultProperties 目前问题1 每个业务方法对应一个兜底的方法,代码膨胀 解决方法 友情提示:通过@DefaultProperties注解实现默认的兜 ...

  6. SpringCloud Openfeign

    文章目录 一. Openfeign简介 二. Openfeign的实现 1 创建 springcloudopenfeign 项目 2 创建 feigncommons 子模块 3 创建 feignapi ...

  7. SpringCloud OpenFeign 服务调用传递 token

    业务场景 通常微服务对于用户认证信息解析有两种方案 在 gateway 就解析用户的 token 然后路由的时候把 userId 等相关信息添加到 header 中传递下去. 在 gateway 直接 ...

  8. J360-cloud SpringCloud系列二:服务发现Discovery Service

    2019独角兽企业重金招聘Python工程师标准>>> j360开源博客之 ----------------------------------------------------- ...

  9. SpringCloud系列二:Restful 基础架构(搭建项目环境、创建 Dept 微服务、客户端调用微服务)...

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:Restful 基础架构 2.具体内容 对于 Rest 基础架构实现处理是 SpringCloud 核心所在,其基本操 ...

  10. Spring-Cloud | openfeign使用细节

    2019独角兽企业重金招聘Python工程师标准>>> 写在前面的话 各位,下午好! 我比较喜欢用 fegin 来实现微服务之间的调用,但是feign使用的那些细节,是get到了吗? ...

最新文章

  1. 【java】兴唐第二十九节课作业
  2. 解题报告:luogu P2341 受欢迎的牛(Tarjan算法,强连通分量判定,缩点,模板)
  3. 2015年7月VIP内推前端工程师面试经历
  4. 推荐系统-07-lambda架构
  5. 写一个判断素数的函数,在主函数输入一个整数,输出是否是素数的消息。
  6. codeforces1486 F. Pairs of Paths(倍增+树上数数)
  7. redis和memcache的对比
  8. 修改定时任务不重启项目,SpringBoot如何实现?
  9. scala从url或者其他数据源读取数据
  10. 使用python读取mid/mif文件
  11. Windows系统下GIT生成密钥和添加密钥git
  12. petalinux 设备树驱动GPIOLED
  13. 两只小企鹅(Python实现)
  14. java爬虫写一个百度图片下载器
  15. 程序员必备5个编程自学网站,你都用到过吗?
  16. C语言实现顺序表(顺序存储结构)
  17. php球鞋,行家啊?!这些球鞋外号你必须要知道!
  18. git tag 标签重命名
  19. 详解|一级建造师考试报名流程有哪些?
  20. HTC G8 wildlife 玩转

热门文章

  1. WordPress插件:WP-China-Yes解决国内访问官网慢的方法
  2. c语言串口接收的字符转int,从串口发送和接收int值
  3. 教育部计算机考研大纲,2021考研计算机大纲计算机组成原理部分考查内容
  4. 算法:求岛屿的数量200. Number of Islands
  5. 剑指 Offer II 092. 翻转字符
  6. 逻辑回归与线性回归是什么关系呢?
  7. 【从线性回归到BP神经网络】第二部分:线性回归
  8. 2015-11-19 22:34:54
  9. 2017.7.31.生活记录
  10. android studio for android learning (十四) android的数据的存储sharedPreferences