OpenFeign 源码解读:动态代理+负载均衡实现

OpenFeign使用@EnableFeignClients开启服务,该注解标有@Import(FeignClientsRegistrar.class),该ImportBeanDefinitionRegistrar会利用扫描路径的方式扫描java文件中带有的@FeignClient(...)的接口,关于这种扫描注解的方式,我仿照写了简化实现:mini-explore

注意,@FeignClient只能注解在接口上。且该接口的所有方法都必须有@GetMapping/@PostMapping注解,此处由feign.Contract接口的实现类SpringMvcContract控制。

OpenFeign通过对注解了@FeignClient的类生成代理该类的BeanDefinition,并默认设置该BeanDefinition.setPrimary(true),所以即使存在重复类型的bean,依然会走OpenFeign的代理。

代理该类的BeanDefinition通过如下方式获取:

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {
		...
		// 虽然为FactroyBean接口实现类
		// 但该FactroyBean并未注入容器
		// 只是为了获取代理,完全可以不用实现FactoryBean接口
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		// 支持${}占位符,其中会使用Environment extends PropertyResolver#resolvePlaceholders
		// url也支持占位符 
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		...
		// 提供Supplier<?>
		// 在spring依赖注入时,会走这个匿名类的Supplier获取代理类对象
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			...
			// *********获取代理对象*********
			return factoryBean.getObject();
		});

		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		// 获取beanDefinition
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");
		beanDefinition.setPrimary(primary);
		
		// 注册beanDefinition
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

由于实现了懒加载,所以并不会在应用启动时就开始OpenFeign生成代理的流程。当用到@FeignClient的接口中的方法时,spring会自动依赖注入动态代理接口的类,此时会使用BeanDefinitionBuilder.genericBeanDefinition(Class,Supplier)方法提供的Supplier<?>来返回对象,该逻辑在AbstractAutowireCapableBeanFactory中,如下:

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory {

	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		...
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		...
	}

}

最终会到FeignClientFactoryBean#getObject中拿代理对象,动态代理和负载均衡实现的入口。

public class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
	
	@Override
	public Object getObject() {
		return getTarget();
	}

	<T> T getTarget() {
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		// BUILDER
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {
			// 没有提供url,则根据name进行负载均衡
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		
		// 提供了url,则直接连接url
		Client client = getOptional(context, Client.class);
		
		builder.client(client);
		applyBuildCustomizers(context, builder);

		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}

}

Targeter#target最终会调用ReflectiveFeign#newInstance

public class ReflectiveFeign extends Feign {
	
	@Override
  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 
      // 是否是接口的default的方法
	  if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      }
      // 加入MethodHandler的实现类SynchronousMethodHandler
      // SynchronousMethodHandler委托Client最终执行远程调用
      else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // JDK动态代理
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    ...
    return proxy;
  }

}

MethodHandler的实现类SynchronousMethodHandler委托给Client#execute接口实现类最终执行远程调用,Client接口实现类中可以实现负载均衡,注意OpenFeign的负载均衡需要引入spring-cloud-starter-loadbalancer依赖。
在这里插入图片描述
上面的LoadBalanceClient用于代理一个Client做负载均衡和重试,最终调用的被代理的Client实现了Http远程调用的功能,而Http远程调用工具有多种,OpenFeign提供了4种Http调用的Client:

  • Client.Default
  • ApacheHttpClient
  • ApacheHttp5Client
  • OkHttpClient

所以上面的FeignBlockingLoadBalancerClientRetryableFeignBlockingLoadBalancerClient可以代理以上的4种,共有4*2=8种配置。

if Spring Cloud LoadBalancer is in the classpath, FeignBlockingLoadBalancerClient is used. If none of them is in the classpath, the default feign client is used

同时,Feign也提供了重试操作,具体的调用多层代理过程如下图(实现类方便举例,可配置更改):
在这里插入图片描述
关于openFeign中的Retryer重试,可在@FeignClient中设置configuration参数,但是在企业中,OpenFeign的接口通常定义为二方包供其他服务调用,不进行复杂的设置,至于OpenFeign调用的重试机制可整合spring-retry实现。

OpenFeign需要依赖loadbalancerorg.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory实现负载均衡(OpenFeign老版本通过Ribbon实现负载均衡)

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

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

相关文章

软件测试 - 测试用例常见面试题

1.测试用例的要素测试用例是为了实施测试而向被测试的系统提供的一组集合, 这组集合包含 : 测试环境, 操作步骤, 测试数据, 预期结果等要素.例如 : 在 B 站输入框输入一个空格, 检查结果测试用例标题 : 输入框输入空格测试环境 : Windows 系统, 谷歌浏览器-版本 111.0.5563.65&…

固态硬盘需要分区吗 固态硬盘怎么分区

磁盘分区是在磁盘中划分几个逻辑部分&#xff0c;来更充分的利用磁盘空间&#xff0c;对保存的数据进行分类储存&#xff0c;方便使用。今天小编给大家介绍一下&#xff0c;固态硬盘需要分区吗&#xff0c;固态硬盘怎么分区。 一、固态硬盘需要分区吗 固态硬盘是需要分区的&a…

Redis:redis通用命令;redis常见数据结构;redis客户端;redis的序列化

一、redis命令 1.redis通用命令 Redis 通用命令是一些 Redis 下可以作用在常用数据结构上的常用命令和一些基础的命令 常见的命令有&#xff1a; keys 查看符合模板的所有key&#xff0c;不建议在生产环境设备上使用&#xff0c;因为keys会模式匹配所有符合条件的key&#…

js常见的9种报错记录一下

js常见报错语法错误(SyntaxError)类型错误(TypeError)引用错误(ReferenceError)范围错误(RangeError)运行时错误(RuntimeError)网络错误&#xff08;NetworkError&#xff09;内部错误&#xff08;InternalError&#xff09;URI错误&#xff08;URIError&#xff09;eval错误&a…

electron+vue3全家桶+vite项目搭建【五】集成Pinia全局状态管理

文章目录引入1.引入依赖2.集成Pinia3.使用pinia4.测试效果引入 在vue2的体系中&#xff0c;vuex是官方推荐的状态管理工具&#xff0c;而vue3的体系中&#xff0c;官网同样推荐了一款状态管理工具&#xff0c;他就是 Pinia Pinia官网 demo项目地址 1.引入依赖 npm install…

docker 安装运行 nacos2.0.3

目录 1、拉取镜像 2、挂载目录 mkdir -p /opt/nacos/logs/ #新建logs目录mkdir -p /opt/nacos/conf/ #新建配置目录vim /opt/nacos/conf/application.properties #修改配置文件 3、application.properties内容 4、初始化nacos的脚…

Vue的简单介绍

一、简介 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;…

生成式 AI 背后的共同框架:Stable Diffusion、DALL-E、Imagen

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 框架 这些生成式 AI 的整体功能为&#xff1a;输入「文字」&#xff0c;返回「图像」&#xff0c;即 Text-to-image Gener…

DBeaver安装教程及基础使用手册

目录 一、简介 基本特性 二、DBeaver安装 三、连接SQL方法 一、简介 DBeaver是免费和开源&#xff08;GPL&#xff09;为开发人员和数据库管理员通用数据库工具。 它支持任何具有一个JDBC驱动程序数据库&#xff0c;也可以处理任何的外部数据源。 DBeaver 通过 JD…

自动化运维软件ansible

一、ansible 基于python语言。简单快捷&#xff0c;被管理端不需要启服务。直接走ssh协议,需要验证&#xff0c;所以机器多的话速度会较慢。 1、ansible环境搭建 5.确认和配置yum源(需要epel源) 免密登录复制的时候可以直接 写ip 不加参数-i 2、服务器分组&#xff08;主机清单…

java的Lambda表达式与方法引用详解

1. 定义 Lambda 表达式&#xff0c;也可称为闭包&#xff0c;它是推动 Java 8 发布的最重要新特性。 Lambda 允许把函数作为一个方法的参数&#xff08;函数作为参数传递进方法中&#xff09;。 使用 Lambda 表达式可以使代码变的更加简洁紧凑。 1.1 通用定义 lambda 表达…

知识图谱实战应用4-知识图谱中寻找相似用户(协同过滤算法)

大家好&#xff0c;我是微学AI&#xff0c;今天给大家讲一下知识图谱中利用协同过滤算法寻找相似用户。大家会看到一个新的名词&#xff1a;“协同过滤”&#xff0c;下面来介绍一下协同过滤算法。 一、协同过滤算法 协同过滤算法是一种基于用户行为分析的推荐算法。它的基本…

php微信小程序java+Vue高校课程课后辅导在线教育系统nodejs+python

目 录 1绪论 1 1.1项目研究的背景 1 1.2开发意义 1 1.3项目研究现状及内容 5 1.4论文结构 5 2开发技术介绍 7 2.1 B/S架构 7 2.2 MySQL 介绍 7 2.3 MySQL环境配置 7 2.5微信小程序技术 8 3系统分析 9 3.1可行性分析 9 3.1.1技术可行性 9 3.1.2经济可行性 9 3.1.3操作可行性 10 …

MySQL的查询完结,vju树状题组完结,cf补题

目录 MySQL 查询 比较条件 判空 逻辑条件 模糊条件 where in 聚合查询 排序查询 vju 线段树OR树状数组 - Virtual Judge cf Problem - A - Codeforces Problem - A - Codeforces Problem - B - Codeforces 周总结 MySQL 查询 比较条件 SELECT *FROM student WH…

细思极恐,第三方跟踪器正在获取你的数据,如何防范?

细思极恐&#xff0c;第三方跟踪器正在获取你的数据&#xff0c;如何防范&#xff1f; 当下&#xff0c;许多网站都存在一些Web表单&#xff0c;比如登录、注册、评论等操作需要表单。我们都知道&#xff0c;我们在冲浪时在网站上键入的数据会被第三方跟踪器收集。但是&#x…

[C++]C++基础知识概述

目录 C基础知识概述&#xff1a;&#xff1a; 1.什么是C 2.C发展史 3.C关键字 4.命名空间 5.C的输入输出 6.缺省参数 7.函数重载 8.引用 9.内联函数 10.auto关键字(C11) 11.基于范围的for循环(C11) 12.指针空值—nullptr(C11) C基础知识概述&#xff1…

React中使用lodash防抖失效解决

React中使用lodash防抖失效解决 import {Input} from antd; import lodash from lodash; // lodash下的防抖函数 const debounce lodash.debounce; // 防抖打印&#xff0c;希望输入的时候&#xff0c;延迟0.5s后打印值 const getSuggestion debounce((val:string) > {co…

SpringCloud微服务技术栈.黑马跟学(九)

SpringCloud微服务技术栈.黑马跟学 九今日目标1.分布式事务问题1.1.本地事务1.2.分布式事务1.3.演示分布式事务问题2.理论基础2.1.CAP定理2.1.1.一致性2.1.2.可用性2.1.3.分区容错2.1.4.矛盾2.2.BASE理论2.3.解决分布式事务的思路3.初识Seata3.1.Seata的架构3.2.部署TC服务一、…

GPT-4老板:AI可能会杀死人类,已经出现我们无法解释的推理能力

来源: 量子位 微信号&#xff1a;QbitAI “AI确实可能杀死人类。” 这话并非危言耸听&#xff0c;而是OpenAI CEO奥特曼的最新观点。 而这番观点&#xff0c;是奥特曼在与MIT研究科学家Lex Fridman长达2小时的对话中透露。 不仅如此&#xff0c;奥特曼谈及了近期围绕ChatGPT…

《统计学习方法》学习笔记之第一章

统计学习方法的学习笔记&#xff1a;第一章 目录 第一节 统计学习的定义与分类 统计学习的概念 统计学习的分类 第二节 统计学习方法的基本分类 监督学习 无监督学习 强化学习 第三节 统计学习方法三要素 模型 策略 第四节 模型评估与模型选择 训练误差与测试误差 过…