@FeignClient源码浅析

news2024/11/17 10:02:41

Spring如何识别@FeignClient

从@EnableFeignClients 出发,寻找Spring如何识别FeignClient

从源码中查看到@Import(FeignClientsRegistrar.class)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
// 省略内部代码
}

查看FeignClientsRegistrar 的类图如下,

ResourceLoaderAware 注入 ResourceLoader

EnvironmentAware 注入 Environment

ImportBeanDefinitionRegistrar: 注册额外的beanDefinition

  ImportBeanDefinitionRegistrar# registerBeanDefinitions ,FeignClientsRegistrar 的实现如下:

    @Override
	public void registerBeanDefinitions( 
        AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 处理@EnableFeignClients上的defaultConfiguration配置
		registerDefaultConfiguration(metadata, registry);
        // 处理@FeignClient注解
		registerFeignClients(metadata, registry);
    }

查阅registerFeignClients 部分的代码,大致逻辑为找到@FeignClient标注的接口,注册到Spring,那注册到Spring的Bean是什么呢??

我们一起来查看下registerFeignClient 方法, FeignClientFactoryBean 就是我们要找的主角了。

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
    // 省略大部分代码
	}

至此我们可以得出如下结论:

 FeignClientsRegistrar 实现了ImportBeanDefinitionRegistrar# registerBeanDefinitions

方法,内部扫描@FeignClient注解的接口,转化为 FeignClientFactoryBean  注入Spring。

FeignClientFactoryBean  做了什么?

 这里我们关注FactoryBean#getObject,(其他扩展点从源码中查看并不重要)

getObject 委托给了getTarget(): 内部代码,有2点关注,

一个就是说明了负载均衡的client是FeignBlockingLoadBalancerClient,

二,最终委托给了,org.springframework.cloud.openfeign.Targeter.target 

Targeter 的默认实现是DefaultTargeter ,内部调用了Feign#target

Feign#target 的代码如下:

    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

    public Feign build() {
      Client client = Capability.enrich(this.client, capabilities);
      Retryer retryer = Capability.enrich(this.retryer, capabilities);
      List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
          .map(ri -> Capability.enrich(ri, capabilities))
          .collect(Collectors.toList());
      Logger logger = Capability.enrich(this.logger, capabilities);
      Contract contract = Capability.enrich(this.contract, capabilities);
      Options options = Capability.enrich(this.options, capabilities);
      Encoder encoder = Capability.enrich(this.encoder, capabilities);
      Decoder decoder = Capability.enrich(this.decoder, capabilities);
      InvocationHandlerFactory invocationHandlerFactory =
          Capability.enrich(this.invocationHandlerFactory, capabilities);
      QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);

      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }

ReflectiveFeign 是其内部实现 。

这段代码的大致逻辑如下:

 总结下就是:

FeignClientFactoryBean#getObject  第一步 Feign.Builder#build() 创建ReflectiveFeign

之后我们来看ReflectiveFeign# newInstance()

  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

内部是JDK的动态代理,核心逻辑在ReflectiveFeign.FeignInvocationHandler

 FeignClientFactoryBean#getObject  第二步 依托ReflectiveFeign #newInstance ,使用JDK动态代理实现,对接口的增强,ReflectiveFeign.FeignInvocationHandler 有调用的逻辑

ReflectiveFeign.FeignInvocationHandler 逻辑

static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }

      return dispatch.get(method).invoke(args);
    }

 策略模式,具体的执行委托给了MethodHandler ,MethodHandler的一个实现是SynchronousMethodHandler

代码很长,这里跳过,直接给出最终的 逻辑调用链条

 FeignInvocationHandler  策略模式,委托给MethodHandler

 SynchronousMethodHandler 底层依托于LoadBalanceClient 实现负载均衡

 LoadBalanceClient 的实现是FeignBlockingLoadBalanceClient

 LoadBalanceClient#choose  底层依托于ReactiveLoadBalancer#choose

 ReactiveLoadBalancer 的一个实现是NacosLoaderBalance#choose

至此,FeignClient的大致逻辑就分析完了。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyQt结合OpenCV实现实时人流量统计

1. 废话篇&#xff08;可跳过&#xff09; 之前学的基本都是Web端的技术。前两天的面试&#xff0c;让我深入的去学习一下 Qt 技术&#xff0c;了解完概念之后&#xff0c;才知道我之前接触的类 TkInter 技术&#xff0c;有点安卓开发的味道。。。 2. 人流量统计效果图 3. 业务…

L1-027 出租(Python实现) 测试点全过

题目 下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救声&#xff0c;急问这个怎么破。其实这段代码很简单&#xff0c;index数组就是arr数组的下标&#xff0c;index[0]2 对应 arr[2]1&#xff0c;index[1]0 对应 arr[0]8&#xff0c;index[2]3 对应 arr[…

Php Jenkins phpunit配置

目录 作用 前提 安装 安装xUnit插件 win10重启Jenkins 全局环境设置 创建项目配置 描述 源码管理 构建触发器 构建步骤 插件安装 工作空间 php代码phpunit文件示例 项目根目录配置 phpunit.xml Protect/Tests/test_start.php composer.json 作用 jenkins 自动…

【docker部署安装ApiSix】

docker安装ApiSi 常见问题-提前查阅 1-端口被占用 确保所需的所有端口&#xff08;默认的 9080/9091/9443/2379/9000&#xff09;未被其他系统/进程使用 #查询端口占用情况 netstat -antp |grep 9443# 如果端口冲突可尝试修改apisix的端口配置&#xff0c; # 但不建议&#x…

0305kali linux配置运行-docker-macos aarm64

文章目录 1 下载运行2 配置2.1 配置系统环境2.2 配置SSH服务2.3 安装工具 3 问题总结结语 1 下载运行 拉取kali linux镜像 docker pull kalilinux/kali-rolling该镜像为“纯净版”系统&#xff0c;没有任何工具&#xff0c;体积小。下面当我们运行起来之后&#xff0c;到容器中…

GlusterFs 分布式复制卷(Distributed-Replicate)性能测试

目录 fio工具参数解释 Glusterfs 和NFS 性能测试 顺序写&#xff1a; 随机写&#xff1a; 顺序读&#xff1a; 随机读&#xff1a; 随机读写&#xff1a; 参数说明&#xff1a; 测试结论&#xff1a; 与NFS对比 压测对比结果 NFS和GlusterFs的优缺点 NFS的优点 NFS…

基于卷积神经网络VGG的猫狗识别

&#xff01;有需要本项目的实验源码的可以私信博主&#xff01; 摘要&#xff1a;随着大数据时代的到来&#xff0c;深度学习、数据挖掘、图像处理等已经成为了一个热门研究方向。深度学习是一个复杂的机器学习算法&#xff0c;在语音和图像识别方面取得的效果&#xff0c;远远…

综合能源系统(1)——综合能源系统基本定义与内涵

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 综合能源系统基本定义 综合能源系统(Integrated Energy System&#xff0c;IES)的概念最早产生于热电联产领域&#xff0c;侧重于热电系统的协同优化&#xff0c;而后逐渐扩展丰富&#xff0c;涉及电、热、冷…

DEVICENET转MODBUS-TCP网关应用案例

远创智控YC-DNT-TCP连接到DEVICENET总线中做为从站使用&#xff0c;连接到 MODBUS-TCP 总线中做为主站或从站使用。是自主研发的一款 DEVICENET 从站功能的通讯网关。 YC-DNT-TCP常用拓展图 技术指标 网关的 MODBUS 接口可通过拨码选择做为主站&#xff08;客户端&#xff09…

QNAP威联通NAS搭建SFTP服务,并内网穿透实现公网远程访问

文章目录 前言1. 威联通NAS启用SFTP2. 测试局域网访问3. 内网穿透3.1 威联通安装cpolar内网穿透3.2 创建隧道3.3 测试公网远程访问 4. 配置固定公网TCP端口地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址4.3 测试使用固定TCP端口地址远程连接威联通SFTP 转载自远程内…

【算法集训之线性表篇】Day 05

文章目录 题目思路代码实现效果 题目 将两个有序顺序表合并为一个有序顺序表&#xff0c;函数结果返回值为顺序表。 思路 我们可以利用二路归并排序算法中的Merge函数思路&#xff0c;设置两个指针i&#xff0c;j&#xff0c;分别记录在顺序表a和b中的访问位置&#xff0c;再…

【未解决】No rule to make target ‘/usr/lib/x86_64-linux-gnu/libGL.so‘

测试ros自带的PCL1.8是否能用&#xff0c;网上找个测试代码&#xff0c;编译阶段报错&#xff1a; cmake .. -- Could NOT find ensenso (missing: ENSENSO_LIBRARY ENSENSO_INCLUDE_DIR) ** WARNING ** io features related to ensenso will be disabled -- Could NOT find …

Blender环境纹理材质贴图入门教程

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D开发工具链 大家好&#xff0c;今天跟大家分享Blender材质贴图入门图文教程&#xff0c;一套blender的PBR材质包&#xff0c;和HDRI环境纹理贴图&#xff0c;在文末领取&#xff0c;希望能助到大家更高效完成场景练习。 据我了解…

Linux Deploy(一)Linux Deploy简介与软件安装

一、Linux Deploy简介 Linux Deploy可以在安卓机器上使用chroot容器技术运行arm或者x86的Linux系统&#xff0c;利用该技术可以搭建个人服务器&#xff0c;Linux Deploy可运行多种linux发行版的软件&#xff0c;不失为一个好的家庭微型服务系统&#xff0c;如果想把手机弄成微…

【Spark_BigData】期末复习考试——

复习题目 yarn框架中不包含的进程为 Yarn包括两个主要进程:资源管理器Resource-Manager,节点管理器Node-Manager。 Scheduler zookeeper spark SQL 前身 Shark 在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。 HiveContext继承自SQLCon…

安装最新版CMAK,处理报错java.util.NoSuchElementException: key not found: PLAINTEXT

安装最新版CMAK&#xff0c;处理报错java.util.NoSuchElementException: key not found: PLAINTEXT 一、下载CMAK二、解压CMAK三、修改配置文件四、安装jdk11五、启动CMAK六、CMAK界面设置Kafka集群信息七、完整报错八、报错原因九、解决方法 一、下载CMAK CMAK下载地址&#…

将本地项目上传至gitee

一、先去gitee新建仓库 点击新建仓库之后&#xff0c;跳转下图页面 点击创建之后&#xff0c;跳转下图页面 到此为止&#xff0c;这里仓库就创建好了&#xff0c;下面去提交代码 二、本地项目连接远程仓库 1、进入想上传的项目的文件夹&#xff0c;然后右键点击 Git Bash Her…

Stable Diffusion 模特换装 蒙版一键批量提取

有没有想过可以使用算法批量提取图片中模特的服装&#xff0c;然后通过SD进行换装。 一个一个的PS抠图是不是太累&#xff0c;可以使用算法批量提取。相对于 Segment Anything 方法这个比较简单。 文章目录 蒙版批量提取SD换装 蒙版批量提取 import osfrom tqdm import tqdm …

前台测试,工程督导及5G网络优化工程师的区别具体在哪里?

什么是通信? 北邮版《通信原理》教材的第一句话是--通信乃是互通信息。 其实&#xff0c;这句话就说出了通信的本质&#xff0c;通信的目标就是如何让世界的任何人在任何时间&#xff0c;任何地点都进行信息的互通。 大到卫星&#xff0c;小到SIM卡&#xff0c;通信技术覆盖于…

OpenCV实现一张图片的特定区域上添加另一张图片

#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv;int main( ){Mat image= imread