OpenFeign#1 - FeignClient 是如何注册的?

文章目录

  • @EnableFeignClients
    • FeignClientsRegistrar
      • registerDefaultConfiguration
      • registerFeignClients
  • @FeignClient
    • FeignClientFactoryBean
      • FeignContext
      • feign(FeignContext)

@EnableFeignClients

该注解会导致 FeignClientsRegistrar 的注入.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   ...
   Class<?>[] defaultConfiguration() default {};
   ...
}

FeignClientsRegistrar

这是一个 FeignClient 的注册器, 实现了接口 ImportBeanDefinitionRegistrarregisterBeanDefinition(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry); // 1
   registerFeignClients(metadata, registry); // 2
}

registerDefaultConfiguration

其中, registerDefaultConfiguration 负责注册 @EnableFeignClients 的属性 defaultConfiguration 指定的配置类 (注意 name 属性的约定方式 -> FeignClientSpecification).
在这里插入图片描述

调用链:registerBeanDefinitions-registerDefaultConfiguration-registerClientConfiguration:

private void registerClientConfiguration(BeanDefinitionRegistry registry, 
                                         Object name,
                                         Object configuration) {
   // 配置类被注入成 "FeignClientSpecification"
   BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   registry.registerBeanDefinition(
     name + "." + FeignClientSpecification.class.getSimpleName(),
     builder.getBeanDefinition()
   );
}

registerFeignClients

在这里插入图片描述

registerFeignClients则负责注册所有@FeignClient(将标注了该注解的接口注册为 FeignClientFactoryBean). 同时以 name 为标识注册该 FeignClient 的配置类. 调用链:registerBeanDefinitions-registerFeignClients-registerFeignClient

其中, registerFeignClients 方法的具体职责是:

  1. 获取 @EnableFeignClients 的 clients 属性指定的"标注了 @FeignClient 的 Class, 该属性非空时会和第二步互斥.
  2. 如果 @EnableFeignClients 的 clients 属性没有指定, 则会启用基于属性 basePackages 或者服务本身 basePackage 对 @FeignClient 的候选类的扫描.
  3. 注册基于 clientName 的配置对象:
    1. 获取 clientName: contextId || value || name || serviceId [详见: org.springframework.cloud.openfeign.FeignClientsRegistrar#getClientName)]
    2. 调用 registerClientConfiguration(BeanDefinitionRegistry, clientName, 注解上配置的 configuration[])
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
   BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   registry.registerBeanDefinition(
         // 注意 name 的定义方式与 `registerDefaultConfiguration` 的区别
         name + "." + FeignClientSpecification.class.getSimpleName(),
         builder.getBeanDefinition());
}
  1. 注册通过第1步或者第2步获取到的"候选类", 调用 registerFeignClient:
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, 
                                 Map<String, Object> attributes) {
   String className = annotationMetadata.getClassName();
   // 本质上也是注册成 FeignClientFactoryBean
   BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
   ... /* 将注解属性添加到 BeanDefinitionBuilder */
   String alias = contextId + "FeignClient";
   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
   beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
   // has a default, won't be null
   boolean 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);
}

至此, @EnableFeignClients 的流程结束. 最终会输出默认的 FeignClientSpecification 与 FeignClient 配对的 FeignClientSpecification, 和 FeignClientFactoryBean. 整体流程图如下:
在这里插入图片描述
从上一节我们知道, @EnabelFeignclients 会扫描并将标注了 @FeignClient 注解的 Bean 注册成 FeignClientFactoryBean, 下面我们主要看看这个 FactoryBean 的能力.

@FeignClient

从上一节我们知道, @EnabelFeignclients 会扫描并将标注了 @FeignClient 注解的 Bean 注册成 FeignClientFactoryBean, 下面我们主要看看这个 FactoryBean 的能力.

FeignClientFactoryBean

上一节已经提到, @FeignClient 标识的接口会被 “根据 @EnableFeignClients 配置的规则扫描出来” 随后 “注册成 FeignClientFacotryBean(在 registerFeignClient 方法中)”.

补充说明
在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。工厂 Bean 是一种特殊的 Bean,对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,而是会返回执行了工厂 Bean 中 FactoryBean#getObject 逻辑的实例.
来看看 FeignClientFactoryBean 对接口 FactoryBean#getObject() 的实现方式:

<T> T getTarget() {
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);
   if (!StringUtils.hasText(url)) { // 查看@FeignClient的url是否为空
      if (!name.startsWith("http")) {
         url = "http://" + name;
      } else {
         url = name;
      }
      url += cleanPath();
      return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
   }
   if (StringUtils.hasText(url) && !url.startsWith("http")) {
      url = "http://" + url;
   }
   String url = this.url + cleanPath();
   Client client = getOptional(context, Client.class);
   if (client != null) {
      if (client instanceof LoadBalancerFeignClient) {
         // not load balancing because we have a url, but ribbon is on the classpath, so unwrap
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url, but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate()
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

FeignContext

首先看第一行: applicationContext.getBean(FeignContext.class) 从 IOC 容器中获取 FeignContext 的实例. FeignContext 是在 OpenFeignStarter 的 spring.factories 被配置为自动注入的:
在这里插入图片描述
FeignAutoConfiguration 会将 FeignContext 注册到容器中, 同时将 "通过 registerClientConfiguration 注入的由用户指定的配置类(@EnableFeignClients 的 configuration 和 @FeignClient 的 configuration) (FeignClientSpecification) " 设值到 FeignContext 中:
在这里插入图片描述

feign(FeignContext)

接下来我们再回到 FeignClientFactoryBean 的 getTarget() 方法, feign(context) 负责组织 Feign.Builder:
首先, encoder, decoder 和 contract 由 @EnableFeignClients@FeignClient 注解属性 configuration 指定. 其中:

  • encoder: feign.codec.Encoder. 负责在当方法参数没有被标注 @Param 时, 将对象解析成 HTTP 请求体.
  • decoder: feign.codec.Decoder. 负责将 HTTP 响应解析成具体的返回值. 当状态码是 2xx, 并且返回类型不是 void 和 Response 时, 被调用.
  • contract: feign.Contract. 定义了接口能够识别的注解和值.
// org.springframework.cloud.openfeign.FeignClientFactoryBean#feign
protected Feign.Builder feign(FeignContext context) {
  FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
  Logger logger = loggerFactory.create(type);

  Feign.Builder builder = get(context, Feign.Builder.class)
                             .logger(logger)
                             .encoder(get(context, Encoder.class))
                             .decoder(get(context, Decoder.class))
                             .contract(get(context, Contract.class));

  configureFeign(context, builder);
  return builder;
}

补充说明
org.springframework.cloud.openfeign.FeignClientFactoryBean#get
get(FeignContext, Class) 方法和其后续调用逻辑的核心在于基于 FeignContext (extends NamedContextFactory) 创建一个基于 contextId 的 AnnotationConfigApplicationContext, 进而注册当前 (contextId 指定) FeignContext 持有的 List 中对应的 FeignClient 的配置类及其中 @Bean, 见下图:
在这里插入图片描述
在这里插入图片描述
另外, FeignContext 也持有了所有已创建的 AnnotationConfigApplicationContext 与 contextId 的对应关系的 “缓存”:
在这里插入图片描述
整体调用流程如下:
在这里插入图片描述


接着, configureFeign(FeignContext, Feign.Builder), 其作用是根据属性或者配置类(@EnableFeignClients 的 configuration 和 @FeignClient 的 configuration) 配置 Feign.Builder:

// org.springframework.cloud.openfeign.FeignClientFactoryBean#configureFeign
protected void configureFeign(FeignContext context, Feign.Builder builder) {
  FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
  FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
  setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
  if (properties != null && inheritParentContext) {
    if (properties.isDefaultToProperties()) {
      configureUsingConfiguration(context, builder);
      configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
      configureUsingProperties(properties.getConfig().get(contextId), builder);
    } else {
      configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
      configureUsingProperties(properties.getConfig().get(contextId), builder);
      configureUsingConfiguration(context, builder);
    }
  } else {
    configureUsingConfiguration(context, builder);
  }
}

重点关注 getOptional(context, FeignClientConfigurer.class) 子句, 跟进去:
1.org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional:

protected <T> T getOptional(FeignContext context, Class<T> type) {
   return context.getInstance(contextId, type);
}

2.org.springframework.cloud.context.named.NamedContextFactory#getInstance(java.lang.String, java.lang.Class<T>):

public <T> T getInstance(String name, Class<T> type) {
   AnnotationConfigApplicationContext context = getContext(name);
   try {
      return context.getBean(type);
   } catch (NoSuchBeanDefinitionException e) {}
   return null;
}

3.org.springframework.cloud.context.named.NamedContextFactory#getContext:

protected AnnotationConfigApplicationContext getContext(String name) {
   if (!this.contexts.containsKey(name)) {
      synchronized (this.contexts) {
         if (!this.contexts.containsKey(name)) {
            this.contexts.put(name, createContext(name));
         }
      }
   }
   return this.contexts.get(name);
}

4.org.springframework.cloud.context.named.NamedContextFactory#createContext:

protected AnnotationConfigApplicationContext createContext(String name) {
   // AnnotationConfigApplicationContext 是一个 application context. 其继承了 GenericApplicationContext 的全部实现, 同时实现了 AnnotationConfigRegistry. 它额外扩展了能通过扫描 package 或是直接调用注册方法的方式来加载带有特定 Annotation Bean 的功能
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
         context.register(configuration); // 注册配置类及其内部 @Bean
      }
   }
   for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
      if (entry.getKey().startsWith("default.")) {
         for (Class<?> configuration : entry.getValue().getConfiguration()) {
            context.register(configuration); // 注册默认配置
         }
      }
   }
   context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
   context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.<String, Object>singletonMap(this.propertyName, name)));
   if (this.parent != null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
      context.setClassLoader(this.parent.getClassLoader());
   }
   context.setDisplayName(generateDisplayName(name));
   context.refresh(); // ! 刷新 AnnotationConfigApplicationContext
   return context;
}

结束, 现在我们知道了 FeignClient 是如何注册并应用配置类的.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/3300.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何用canvas制作一个华容道小游戏(乞丐版)

我大抵是废了φ(&#xff0e;&#xff0e;) &#xff0c;横竖都学不进去&#xff0c;上课知识不进脑子&#xff0c;学习光想划水摸鱼&#xff0c;心中仅剩的良知告诉我这样下去是铁定不行的哇&#xff0c;既然学不进去&#xff0c;何不打把游戏&#xff0c;既然要打游戏&#x…

HTML5 Video(视频)

HTML5 Video(视频) 在本节内容中&#xff0c;你将了解到在HTML5中视频是如何工作的、主流浏览器支持的视频格式以及如何对网页中的视频进行控制。 很多站点都会使用到视频. HTML5 提供了展示视频的标准。 检测您的浏览器是否支持 HTML5 视频&#xff1a; Web站点上的视频 直…

SeNet论文解读/总结

此文章为深度学习在计算机视觉领域的图片分类经典论文SeNet&#xff08;Squeeze-and-Excitation Networks&#xff09;论文总结。 此系列文章是非常适合深度学习领域的小白观看的图像分类经典论文。系列文章如下&#xff1a; AlexNet&#xff1a;AlexNet论文解读/总结_alexnet…

在CentOS上安装Docker引擎

1,先决条件#### 1-1操作系统要求1-2 卸载旧版本 2,安装方法2-1使用存储库安装设置存储库安装 Docker 引擎 本文永久更新地址: 官方地址&#xff1a;https://docs.docker.com/engine/install/centos/ 1,先决条件 #### 1-1操作系统要求 要安装 Docker Engine&#xff0c;您需要…

【基础算法】链表相关题目

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招算法的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于代码随想录进行的&#xff0c;每个算法代码参考leetcode高赞回答和…

官宣|Apache Flink 1.17 发布公告

Apache Flink PMC&#xff08;项目管理委员&#xff09;很高兴地宣布发布 Apache Flink 1.17.0。Apache Flink 是领先的流处理标准&#xff0c;流批统一的数据处理概念在越来越多的公司中得到认可。得益于我们出色的社区和优秀的贡献者&#xff0c;Apache Flink 在 Apache 社区…

STM32F407控制微型推拉式电磁铁(通过继电器)

1、继电器 继电器相当于开关&#xff0c;单片机通过io口高低电平的控制来控制继电器的开闭。采用继电器的好处除了能够用低电压控制高电压&#xff08;如32单片机控制220V的电压&#xff09;外&#xff0c;还可以防止电流反冲&#xff0c;弄烧单片机。 本文采用3.3v的电磁铁&am…

三、MyBatis核心配置文件详解

核心配置文件中的标签必须按照固定的顺序(有的标签可以不写&#xff0c;但顺序一定不能乱)&#xff1a; properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers 一、…

b01lers(php.galf)

目录 前文 正文 前文 <?phpclass A{public $codeNULL;public $argsNULL;public function __construct($code,$argsNULL){$this->code$code;$this->args$args;print_r("2333") ;} public function __invoke($code,$args){echo $code;print_r("执行inv…

记一次若依后台管理系统渗透

前言 最近客户开始hw前的风险排查&#xff0c;让我们帮他做个渗透测试&#xff0c;只给一个单位名称。通过前期的信息收集&#xff0c;发现了这个站点&#xff1a; 没有验证码&#xff0c;再加上这个图标&#xff0c;吸引了我注意&#xff1a; 从弱口令开始 若依默认口令为ad…

Android 12.0 Settings主页面去掉FocusRecyclerView相关功能

1.前言 在12.0的系统rom产品定制化开发中,在系统Settings主页面的主菜单中,在测试某些功能的时候,比如开启护眼模式和改变系统密度会在主菜单第一项的网络菜单头部增加 自定义您的设备和设置护眼模式时间安排 等等相关的设置模块 这对于菜单布局显示相当不美观,所以根据系…

机器学习---降维算法

知其然知其所以然【写在前面】主成分分析&#xff08;PCA&#xff09;原理部分代码部分可视化部分线性判别分析&#xff08;LDA&#xff09;原理部分代码部分可视化部分独立成分分析&#xff08;ICA&#xff09;原理部分代码部分可视化部分t-SNE降维算法原理部分代码部分可视化…

请求响应数据?Controler层注解!

目录1. 请求1.1概述1.2 简单参数1.2.1 原始方式1.2.2 SpringBoot方式1.2.3 参数名不一致1.3 实体参数1.3.1 简单实体对象1.3.2 复杂实体对象1.4 数组集合参数1.4.1 数组1.4.2 集合1.5 日期参数1.6 JSON参数1.7 路径参数2. 响应2.1 ResponseBody2.2 统一响应结果1. 请求 1.1概述…

Hive数据仓库简介

文章目录Hive数据仓库简介一、数据仓库简介1. 什么是数据仓库2. 数据仓库的结构2.1 数据源2.2 数据存储与管理2.3 OLAP服务器2.4 前端工具3. 数据仓库的数据模型3.1 星状模型3.2 雪花模型二、Hive简介1. 什么是Hive2. Hive的发展历程3. Hive的本质4. Hive的优缺点4.1 优点4.2 缺…

Vue2响应式原理

目录 Object.defineProperty() 监听对象中的简单数据类型 监听对象中的对象(可以深层) 监听对象中的数组 借鉴的帖子&#xff1a;Object.defineProperty方法&#xff08;详解&#xff09;_objectdefineproperty_搞前端的小菜的博客-CSDN博客 b站视频讲解&#xff1a;Vue2响…

学习 Python 之 Pygame 开发魂斗罗(十三)

学习 Python 之 Pygame 开发魂斗罗&#xff08;十三&#xff09;继续编写魂斗罗1. 创建敌人2类2. 编写敌人2类的draw()函数3. 编写敌人越界消失函数4. 编写敌人开火函数5. 把敌人2加入地图进行测试继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗&#xff08;十…

Adapter基础讲解

这一节我们要讲的UI控件都是跟Adapter(适配器)打交道的,了解并学会使用Adapter很重要, Adapter是用来帮助填充数据的中间桥梁,简单来说就是:将各种数据以合适的形式显示到view上,提供 给用户看! 1.MVC模式的简单理解 在开始学习Adapter之前我们要来了解下这个MVC模式概…

SpringBoot——Mybatis-XML映射文件—动态SQL

使用XML映射文件配置SQL语句的规范 XML文件当中的Mapper标签里面使用的select标签的id属性是Mapper接口里面的方法名&#xff0c;resultType属性名是SQL语句要返回的对象类型。 MybatisX插件 用于管理接口方法和映射文件的关系 注解和XML配置SQL语句两种选择 动态SQL——w…

Cookie 和 Session的区别

文章目录时间&#xff1a;2023年3月23日第一&#xff1a;什么是 Cookie 和 Session ?什么是 Cookie什么是 Session第二&#xff1a;Cookie 和 Session 有什么不同&#xff1f;第三&#xff1a;为什么需要 Cookie 和 Session&#xff0c;他们有什么关联&#xff1f;第四&#x…

由本溯源,带你探索BI实时性的本质

BI 采用T1模式 BI的数据仓库架构本身就决定了对数据的实时性要求没有那么高&#xff0c;ETL的过程不可或缺&#xff0c;Extraction 抽取、Transformation 转换、Loading 加载&#xff0c;这三个环节本身就是有时间损耗的。 BI - 派可数据BI可视化分析平台 首先&#xff0c;Ex…