Feign源码解析:初始化过程(一)
前言
打算系统分析下Feign的代码,上一篇讲了下Feign的历史,本篇的话,先讲下Feign相关的beanDefinition,beanDefinition就是bean的设计图,bean都是按照beanDefinition来制造的。
Feign相关的bean不少,有一些是因为我们的Feign相关注解而引入的,有一部分是因为spring的自动装配来自动引入的。
今天讲讲因为我们注解引入的那些。
EnableFeignClients引入的FeignClientSpecification
如果项目用到Feign,在@SpringBootApplication注解的主类上,我们一般还会加上@EnableFeignClients注解。
package a.b.c;
@SpringBootApplication
@EnableFeignClients
public class DemoApplication
实际上,spring的beanFactory初始化一般就是分两步,第一步,收集beanDefinition列表,第二步,根据beanDefinition生成并初始化bean。
收集beanDefinition相当重要,但是beanDefinition从哪来来呢,一开始的时候,都是框架默认注册了几个,应用自身的beanDefinition一般要从主类来,也就是上面说的@SpringBootApplication注解的主类。
一开始就会去解析主类上的注解,包名;解析包名的原因是,拿到包名后,默认就会去扫描这个包名下的class,再找到注解了@configuration、@controller、@service、@component之类的bean作为beanDefinition;解析注解的原因是,可以根据注解,引入更多的beanDefinition。
以@EnableFeignClients为例,该注解的定义如下:
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients
所以这里会通过Import引入更多的beanDefinition。
org.springframework.cloud.openfeign.FeignClientsRegistrar
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1
registerDefaultConfiguration(metadata, registry);
// 2
registerFeignClients(metadata, registry);
}
上面的1处,如下:
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
String name = "default." + metadata.getClassName();
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(builder.getBeanDefinition());
}
主要是获取@EnableFeignClients这个注解相关的默认属性,然后注册了一个FeignClientSpecification类型的bean。
这个FeignClientSpecification类很简单,属性就两个:
public class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
一个name,一个configuration,其实就是代表了一个要如何创建和配置FeignClient的配置,包含了如何创建feign的encoder、decoder等。
A custom @Configuration for all feign clients. Can contain override @Bean definition for the pieces that make up the client, for instance feign.codec.Decoder, feign.codec.Encoder, feign.Contract.
我们举个例子,如下的代码,
package a.b.c;
@SpringBootApplication
@EnableFeignClients
public class DemoApplication
最终生成的FeignClientSpecification beanDefinition,beanName为:default.a.b.c.DemoApplication.FeignClientSpecification,属性:
name:default.a.b.c.DemoApplication
configuration:空数组
FeignClient注解引入的FeignClientSpecification
说完这个,继续说如下的二处:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1
registerDefaultConfiguration(metadata, registry);
// 2
registerFeignClients(metadata, registry);
}
2处内部,是首先获取@EnableFeignClients注解的类所在的包名,然后在这个包名下扫描注解了@FeignClient的类,扫描到的这些类,那肯定是弄了一个beanDefinition了,这个可以倒推,毕竟每个@FeignClient注解的类最终都变成了一个bean嘛。
里面有一点值得讲,FeignClient注解中有一个属性,叫configuration,它支持你指定一个class,里面可以覆盖FeignClient内部的部分组件,如Feign的Encoder等
public @interface FeignClient {
String value() default "";
Class<?>[] configuration() default {};
}
这个你指定的配置类,是可以被多个FeignClient复用的,所以,spring内部也是只会存一份,这一份配置,就通过一个创建一个类型为FeignClientSpecification.class的bean来保存。
以如下为例:
@FeignClient(name = "demoService")
public interface DemoServiceFeignClient
由于没有指定configuration属性,这里生成的FeignClientSpecification bean中,name就是demoService,configuration就是null:
public class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
如果指定configuration属性:
@FeignClient(name = "demoService", configuration = CustomConfig.class)
public interface DemoServiceFeignClient
FeignClientSpecification中的configuration就会是CustomConfig.class。
这个FeignClientSpecification.class的bean的名字,默认则会是demoService.FeignClientSpecification。
FeignClient对应的beanDefinition
接下来,就是注册一个FeignClient对应的beanDefinition了。
详细的源码可以看这里:org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
FeignClient的bean是较难创建的,所以这里是用了工厂bean:FeignClientFactoryBean
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
...
return factoryBean.getObject();
});
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
这里的beanname,一般也就是FeignClient的全路径名。
汇总
最终其实就引入了三个bean:

首先是由@enableFeignClients引入的FeignClientSpecification,然后是@enableFeignClients注解所在的包名下的由@FeignClient注解引入的FeignClientSpecification,再一个就是FeignClient本身的bean(一个factoryBean)
Feign源码解析:初始化过程(一)的更多相关文章
- Feign源码解析
1. Feign源码解析 1.1. 启动过程 1.1.1. 流程图 1.1.2. 解释说明 Feign解析过程依赖Spring的初始化,它通过实现ImportBeanDefinitionRegistr ...
- Feign源码解析系列-注册套路
感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注. 这篇会详细解析Feign Client配置和初始化的方式,这些方式大 ...
- junit源码解析--初始化阶段
OK,我们接着上篇整理.上篇博客中已经列出的junit的几个核心的类,这里我们开始整理junit完整的生命周期. JUnit 的完整生命周期分为 3 个阶段:初始化阶段.运行阶段和结果捕捉阶段. 这篇 ...
- springmvc源码解析-初始化
1. 概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根 ...
- mybatis源码阅读-初始化过程(七)
说明 mybatis初始化过程 就是解析xml到封装成Configuration对象 供后续使用 SqlSessionFactoryBuilder 代码例子 SqlSessionFactoryBuil ...
- Feign源码解析系列-那些注解们
开始 Feign在Spring Cloud体系中被整合进来作为web service客户端,使用HTTP请求远程服务时能就像调用本地方法,可见在未来一段时间内,大多数Spring Cloud架构的微服 ...
- 分布式事务_02_2PC框架raincat源码解析-启动过程
一.前言 上一节已经将raincat demo工程运行起来了,这一节来分析下raincat启动过程的源码 主要包括: 事务协调者启动过程 事务参与者启动过程 二.协调者启动过程 主要就是在启动类中通过 ...
- Feign源码解析系列-最佳实践
前几篇准备写完feign的源码,这篇直接给出Feign的最佳实践,考虑到目前网上还没有一个比较好的实践解释,对于新使用spring cloud的同学会对微服务之间的依赖产生一些迷惑,也会走一些弯路.这 ...
- Feign源码解析系列-核心初始化
开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...
- Syncthing源码解析 - 启动过程
我相信很多朋友会认为启动就是双击一下Syncthing程序图标,随后就启动完毕了!如果这样认为,对,也不对!对,是因为的确是这样操作,启动了Syncthing:不对是因为在调试Syncthing启动过 ...
随机推荐
- 【译】在 Visual Studio 中处理图像变得更容易了
任何 Web.桌面或移动开发人员都经常使用图像.你可以从 C#.HTML.XAML.CSS.C++.TypeScript 甚至代码注释中引用它们.有些图像是本地的,有些存在于线上或网络共享中,而其他图 ...
- 设备维修保养通知:如何使用API接口发送通知给相关人员
在设备维修保养管理中,及时通知相关人员是确保设备得到及时维护的关键.API接口提供了一个方便的方式来自动发送维修保养通知,以确保工作流程的顺利进行.本文将详细介绍如何使用成熟的API接口来发送设备维修 ...
- 「openjudge / poj - 1057」Chessboard
link. 调起来真的呕吐,网上又没篇题解.大概是个不错的题. 首先行和列一定是独立的,所以我们把行列分开考虑.这样的问题就弱化为:在一个长度为 \(n\) 的格子带上,有 \(n\) 个物品,每个物 ...
- 「note」原根照抄
阶(multiplicative order) \(\textbf{Def.}\):\(\delta_m(a)\) 为最小的 \(n\) 使得 \(a^n\equiv 1\pmod m\),其中 \( ...
- jdk8环境变量
JAVA_HOME C:\Program Files\Java\jdk1.8.0_333 Path%JAVA_HOME%\bin%JAVA_HOME%\jre\bin CLASSPATH.%JAVA_ ...
- netstat命令输出详解
netstat命令输出详解 1. 列出所有的TCP和UDP端口 2. 命令输出详解 Proto:协议名(tcp协议还是udp协议) recv-Q:网络接收队列,send-Q:网路发送队列 a. rec ...
- strimzi实战之一:简介和准备
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于strimzi strimzi是一个开源项目,已加 ...
- 贝塞尔曲线的切线及其AABB问题
贝塞尔曲线的切线及其AABB问题 先聊点别的 2023 年抖音上居然还看到很多前端培训 各种直播前端教学(虽然是录播)但看起来还是有大批前往前端卷啊 说明了什么,很可能说明其它行业更难卷 这不是行业不 ...
- Go 常用命令介绍
Go 常用命令 目录 Go 常用命令 一.Go 常用命令 1.1 go build 1.1.1 指定输出目录 1.1.2 常用环境变量设置编译操作系统和 CPU 架构 1.1.3 查看支持的操作系统和 ...
- 43道Python经典案例题(有答案)
1.有四个数字:1.2.3.4,能组成多少个互不相同且无重复数字的三位数?各是多少? for x in range(0,5): for y in range(0,5): for z in range( ...