OpenFeign AutoConfiguration源码解析

news2024/11/20 13:17:46

本文约2千字,主要知识

  • OpenFeign的父子容器
  • FeignClient的注册

背景

在使用Spring Cloud时,经常使用OpenFeign 作为远程服务调用的类库;
Feign 是一种声明式服务调用组件,它在 RestTemplate 的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao 接口上面的 Mapper 注解一样)即可实现对 HTTP 接口的绑定。通过 Feign,我们可以像调用本地方法一样来调用远程服务,而完全感觉不到这是在进行远程调用。
Spring Boot3中,也引入了一套全新的声明式HTTP调用,这也可说明声明式的调用方式收到足够的青睐。

环境

Spring Boot : 2.7.6
spring-cloud:2021.0.5
OpenFeign-core : 11.10
jdk:17

示例工程

https://github.com/Fudeveloper/openfeign-demo.git

以下为示例工程的主要类

@FeignClient(name = "api-proxy"
             ,url = "http://127.0.0.1:8080"
             ,configuration = MyApiConfiguration.class)
public interface MyApi {
    @GetMapping("/test/my")
    String testForGet();
}
public class MyApiConfiguration {
    @Bean
    public Encoder myEncoder() {
        return new Encoder() {
            @Override
            public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
                System.out.println("trigger myEncoder#encode");
            }
        };
    }
}

参考资料

OpenFeign 的使用文档

FeignClient BeanDefinition的注册

按照国际惯例,要启用SpringBoot的某个组件,需要先标注@EnableXXX注解;OpenFeign 也不例外,其注解类@EnableFeignClients主要内容如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 核心:导入FeignClientsRegistrar
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	
	String[] basePackages() default {};
	Class<?>[] clients() default {};
	// ... 
}

其中,最核心的动作是@Import(FeignClientsRegistrar.class)
FeignClientsRegistrar 实现了ImportBeanDefinitionRegistrar接口,实现了一套自定义的BeanDefinition注册流程。在Spring applicationContext 容器 refresh的过程中(具体为invokeBeanFactoryPostProcessors->postProcessBeanDefinitionRegistry->processConfigBeanDefinitions)时,将调用FeignClientsRegistrar#registerBeanDefinitions

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 注册 默认配置
        registerDefaultConfiguration(metadata, registry);
        // 注册 FeignClient
        registerFeignClients(metadata, registry);
    }
}

registerFeignClients注册所有@FeignClient

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    	// 保存clients BeanDefinition 的数组
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
            // 如果未手动指定 clients,则扫描后添加
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
            // 已手动指定,直接添加
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
        // ①遍历所有client,并注册
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // ... 

                // 获取@FeignClient 标注类的属性
				Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
                // ② 注册 ${name}.FeignClientSpecification 
				registerClientConfiguration(registry, name, attributes.get("configuration"));
            	// ③ 注册client的bean
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

在①处断点后可以发现,candidateComponents已装载我们自定义的FeignClient
image.png

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);
        // ③.1 构建factoryBean
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
        // 是否可刷新,从feign.client.refresh-enabled配置解析
		factoryBean.setRefreshableClient(isClientRefreshEnabled());

        // 将@FeignClinet标注类的配置信息构建为BeanDefinition
        // clazz即为@FeignClinet标注类的class
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
            // ③.2 注意此处是从factoryBean 获取对象,具体为FeignClientFactoryBean#getObject
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);

		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        // 委托给DefaultListableBeanFactory#registerBeanDefinition
        // ④ 将@FeignClinet标注类注册入Spring IOC父容器
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    	// 支持Request.Options的refresh 操作,非本章重点
		registerOptionsBeanDefinition(registry, contextId);
	}

registerDefaultConfigurationregisterFeignClients ,最终都将调用registerClientConfiguration方法。
② 和③ 方法则将实际操作委托给DefaultListableBeanFactory#registerBeanDefinition;这涉及到Spring IOC的核心流程,此处不再赘述。在本文中只需记住如下现象。若要了解更多相关知识,可参考: 《Spring 揭秘》第二部分:Spring IOC容器
![](https://img-blog.csdnimg.cn/img_convert/183575716fde7fefbf7cffe4f2f9da87.png#averageHue=#f0f0f0&crop=0&crop=0&crop=1&crop=1&from=url&height=296&id=BnAcr&margin=[object Object]&originHeight=323&originWidth=775&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=711)

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
    // 不存在则加入集合
    this.beanDefinitionMap.put(beanName, beanDefinition);
    this.beanDefinitionNames.add(beanName);
}

在③结束后,查看DefaultListableBeanFactory#beanDefinitionMap,可发现我们自定义的FeignClient已注册到IOC 容器中
image.png

FeignContext 容器的实例化

回看③.1 、③.2的操作,即创建FeignClientFactoryBean并从中获取对象;在Spring中,FactoryBean是个举足轻重的类,可参考:What’s a FactoryBean?

注意③.2的后续操作:FeignClientFactoryBean#getObject

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


    <T> T getTarget() {
        // ③.3 以注入的FeignContext,构建Feign
        FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
            : applicationContext.getBean(FeignContext.class);
        // ③.5 构建feign,并创建子容器
        Feign.Builder builder = feign(context);

        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
            // ... 
            builder.client(client);
        }
    	// 应用自定义的配置项
        applyBuildCustomizers(context, builder);

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

    protected Feign.Builder feign(FeignContext context) {
        // ③.5.1 首次从子容器获取bean,将触发子容器创建
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(type);

		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
	
        // ③.5.2  根据FeignClientProperties、FeignClientConfiguration、父容器配置Feign
		configureFeign(context, builder);

		return builder;
	}

    protected <T> T get(FeignContext context, Class<T> type) {
		T instance = context.getInstance(contextId, type);
		if (instance == null) {
			throw new IllegalStateException("No bean found of type " + type + " for " + contextId);
		}
		return instance;
	}

}

根据Spring Boot的SPI机制,可以发现在IOC 容器中引入了FeignAutoConfiguration
image.png
FeignAutoConfiguration主要工作是注入一个FeignContext,与上方③.3 相呼应

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class, FeignEncoderProperties.class })
public class FeignAutoConfiguration {
    // 注入第一节中的所有配置
    @Autowired(required = false)
    private List<FeignClientSpecification> configurations = new ArrayList<>();
    
    // ③.4 构建FeignContext
    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }
}

在③.4 步骤时,断点查看configurations,可看到 步骤② 注册到Spring IOC容器中的FeignClientSpecification
image.png


public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

    public FeignContext() {
        // 注意FeignClientsConfiguration 类,其中存放了默认Bean配置
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }
}

FeignContext继承于NamedContextFactory:NamedContextFactory 可以创建一个子容器(或者说子上下文)。OpenFeign 则是依赖它来实现子容器机制的。

FeignContext 子容器的创建

在③.5中,多处调用了FeignClientFactoryBean#get方法,此方法将调用NamedContextFactory#createContext方法创建子容器。
image.png

// 子容器 map集合
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();


public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    try {
        // 从容器中获取Bean
        return context.getBean(type);
    }
    catch (NoSuchBeanDefinitionException e) {
        // ignore
    }
    return null;
}

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);
}

// 创建子容器的方法
protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context;
    if (this.parent != null) {
        // 处理父类信息
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        if (parent instanceof ConfigurableApplicationContext) {
            beanFactory.setBeanClassLoader(
                ((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
        }
        else {
            beanFactory.setBeanClassLoader(parent.getClassLoader());
        }
        context = new AnnotationConfigApplicationContext(beanFactory);
        context.setClassLoader(this.parent.getClassLoader());
    }
    else {
        // 当前已为顶级
        context = new AnnotationConfigApplicationContext();
    }
    
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
            // 处理@FeignClient 中定义的configuration,对应背景中的`MyApiConfiguration`
            context.register(configuration);
        }
    }
    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) {
        // 设置父容器,目的是让所有bean也使用父容器的上下文信息
        context.setParent(this.parent);
    }
    context.setDisplayName(generateDisplayName(name));
    // 和Spring IOC容器相同,走refresh流程
    context.refresh();
    return context;
}

同样的,在子容器的refresh 过程中(具体为finishBeanFactoryInitialization->beanFactory.preInstantiateSingletons),将初始化子容器所需要的Bean,如背景中定义的MyEncoder,以及FeignClientsConfiguration类中定义的名为feignXX的各个bean。
之后情况则与IOC 容器初始化过程类似,此处不再赘述。
image.png
子容器初始化完成后,执行到③.5.2 处的代码,其根据FeignClientProperties、FeignClientConfiguration、父容器配置Feign;其中,FeignClientConfiguration主要是注入一些默认Bean,如下所示

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
    }

    //... 省略其他默认Encoder、Capability 等
}

之后,执行至④,将@FeignClinet标注类,以bean的形式注册入Spring IOC父容器。至此,FeignClient的注册已完成。

总结

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

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

相关文章

跨平台应用开发进阶(五十)uni-app ios web-view嵌套H5项目白屏问题分析及解决

文章目录一、前言二、问题分析三、解决方案3.1 nvue 页面替代 vue 页面3.2 白屏检测刷新3.2.1 自动刷新3.2.2 手动刷新3.3 总结四、拓展阅读一、前言 应用uni-app框架开发好APP上架使用过程中&#xff0c;发现应用经过长时间由后台切换至前台时&#xff0c;通过webview方式嵌套…

SQL语句(基本)

SELECT 语句的 执行过程&#xff1a; from clause ---> where clause ---> select --->group by ---> having--->order by ---> limit 写法顺序: select col1,... from clause ---> where clause ---> group by ---> having---> order by --->…

“ 请你要发光 而不是被照亮 “

做一个厉害的大人 勇敢地长大 成为会发光的星星 勇音频&#xff1a;00:0003:41 | 01 | 世界不会辜负努力拼搏的人 光明的前途在乌云散去之后 请你一定一定坚持自己 勿忘初心 要做会发光的星星 成为想成为的大人啊 | 02 | 我牵起你的手 你望向我的眼 少了你的懵懂青涩…

总线一:IIC

一、I2C集成电路总线, 多用于主控制器和从器件间的主从通信。 二、适用场景&#xff1a;在小数据量场合使用&#xff0c;传输距离短。 三、IIC是半双工。IIC的物理层&#xff1a;两条总线线路&#xff0c;一条是串行数据线SDA&#xff0c;一条是串行时钟线SCL&#xff0c;当总…

《Python知识手册》更新到V4.1版,快拿走学习

前言 最近&#xff0c;我花了点时间&#xff0c;把《Python知识手册》的部分内容进行了更新&#xff0c;更新后的版本号为 v4.1 版。 python知识手册内容&#xff1a;《Python知识手册》 没有比较完整的覆盖 Python 的基础知识。因此&#xff0c;针对手册的阅读&#xff0c;各…

程序员年底好找工作吗?

到年底了除非必要不要辞职&#xff01;除非必要不要辞职&#xff01;除非必要不要辞职&#xff01; 重要的事情说三遍。 很多老哥问我&#xff1a;工作干不下去了&#xff0c;这会儿辞职找工作合适吗&#xff1f;今天就来为大家解答一下&#xff0c;为什么不要在年底辞职&…

Redis6入门到实战------ 六、Redis_Jedis_测试

1 Jedis所需要的jar包 在pom文件中引入依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency>2 连接Redis注意事项 禁用Linux的防火墙&#xff1a;L…

Linux mybash

shell 在了解bash之前 我们要先了解shell Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。 Shell 是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面…

NoSQL数据库原理与应用综合项目——Neo4j篇

NoSQL数据库原理与应用综合项目——Neo4j篇 文章目录NoSQL数据库原理与应用综合项目——Neo4j篇0、 写在前面1、本地数据或HDFS数据导入到Neo4j2、Neo4j数据库表操作2.1 使用Python连接Neo4j2.2 查询数据2.3 插入数据2.4 修改数据2.5 删除数据3、Windows远程连接Neo4j(Linux)4、…

uboot源码下载以及编译

环境&#xff1a;ubuntu 20.04 uboot源码下载以及编译1 uboot源码下载&#xff1a;1.1 进入uboot官网1.2 下载源码2 编译uboot2.1 配置2.2 编译2.2.1 确认编译工具链是否ok2.2.2 配置环境变量2.2.3 编译3 注意事项3.1 uboot 2022.04版本问题3.2 unable to execute swig: No suc…

分代收集算法

将这个堆内存划分成两块&#xff1a;新生代和老年代&#xff0c;刚刚创建的对象都在新生代&#xff0c;长久存活的对象都在老年代&#xff08;老年代的垃圾回收很久发生一次&#xff0c;新生代的垃圾回收发生的比较频繁&#xff09; 新生代又进一步划分成伊甸园Eden&#xff0…

docker安装gitlab(超级详细)

前提&#xff1a; 在操作之前需要安装docker和docker-compose 拉取镜像 docker pull gitlab/gitlab-ce:15.2.4-ce.0 创建本地文件夹 mkdir /data/docker/gitlab/etc mkdir /data/docker/gitlab/logs mkdir /data/docker/gitlab/data 编写 docker-compose.yml cd /data/docker/…

面试高频题目,每周更新。

1.如何实现一个div快速的居中对齐&#xff1f; 2.margin和padding有什么不同&#xff1f; 作用对象不同&#xff0c;margin是针对对外部对象&#xff0c;padding是针对于自身。 3.vw和百分比有什么区别&#xff1f; 百分比是有继承的&#xff0c;父级元素改变后&#xff0c;会…

删除的照片如何恢复? 5个照片恢复方法总结

有人说&#xff0c;照片承载着很多回忆&#xff0c;一些难忘的时刻&#xff0c;还有一些经历。这就是我们不能丢失它们的原因&#xff0c;如果偶然丢失它们&#xff0c;到目前为止还没有可靠的设备。但是&#xff0c;如果您丢失了一些照片并想找回它们&#xff0c;请不要担心&a…

MySQL集群解决方案(4):负载均衡

在前面架构中&#xff0c;虽然对mycat做了集群&#xff0c;保障了mycat的可靠性&#xff0c;但是&#xff0c;应用程序需要连接到多个mycat&#xff0c;显然不是很友好的&#xff0c;也就是说缺少负载均衡的组件&#xff0c;接下来我们来了解下HAProxy。 1 简介 官网&#xff…

Nacos安装-单机安装

目录 一、环境介绍 二、安装方式 三、部署模式 四、单机模式包部署 4.1 下载到对应地址 4.2 解压缩包 4.3 单机模式支持mysql 导入初始化SQL 修改配置文件 4.4. 单机模式启动nacos 启动成功 登录 一、环境介绍 操作系统&#xff1a;Ubuntu 20.04.1JDK&#xff1a;保证…

HaaS EDU物联网项目实战:老板来了

HaaS EDU K1是一款高颜值、高性能、高集成度的物联网开发板&#xff0c;板载功能强大的4核&#xff08;双核300Mhz M33双核1GHz A7&#xff09;主芯片&#xff0c;2.4G/5G双频Wi-Fi&#xff0c;双模蓝牙&#xff08;经典蓝牙/BLE&#xff09;&#xff0c;并自带丰富的传感器与小…

2023 年的 7 个技术趋势——人工智能与企业内容管理相结合,云成为企业 IT 标准, 数据民主化······

2023 年的 7 个技术趋势 技术的发展速度比以往任何时候都快。高通胀、供应链问题和技术工人短缺等经济因素给当今的企业带来了巨大的压力。艰难的经济环境促使公司重新评估其财务战略&#xff0c;并寻找保持利润率的方法&#xff0c;同时培养对不断变化的经济状况做出快速反应…

又一款AR HUD前装量产上车!这家供应商深耕HUD行业8年

12月15日晚&#xff0c;岚图汽车首款轿车——岚图追光正式首发亮相并开启预售。预售价分别为32.29万元、35.29万元、43.29万元。 作为岚图汽车首个搭载ESSASOA智能电动仿生体的量产车型&#xff0c;领先的技术架构保证岚图追光在性能、智能、豪华、安全等各维度的表现都达到同级…

【数据结构-排序】内部排序

文章目录1 直接插入排序1.1 算法简要思想1.2 算法特性2 希尔排序2.1 算法简要思想2.2 手动模拟2.3 算法特性3 冒泡排序3.1 算法简要思想3.2 算法特性4 快速排序4.1 算法思路4.2 算法代码4.3 手动模拟4.4 算法特性4.5 相关例题5 简单选择排序5.1 算法简要思想5.2 算法特性6 堆排…