👈👈👈 欢迎点赞收藏关注哟
首先分享之前的所有文章 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164...
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca...
一. 前言
Nacos 作为配置中心和服务注册这一点已经没什么好说的了,但是如果聊到nacos 配置如何生效这个原理,涉及的点就很多了。
二. 宏观思想
在整个配置的处理过程中,可以看成2个阶段 :
👉阶段一 : 从各个渠道收集数据,存放在 Enviroment
对象中 , 在 Enviroment 对象中会存在一个 PropertySourceList
的列表, 用来存放各种 PropertySource , 比如 BootstrapPropertySource
或者 MapPropertySource
👉阶段二 : 将收集到的所有的数据 ,在 populateBean
环节进行数据的写入
而在这2个环节中,又插入了 Nacos 的配置拉取 ,Bean 的创建 ,@Resource 的导入 ,@Value 的导入多个概念。
👇 下面会从头开始依次把这个环节聊清楚 :
三. 详细流程
3.1 Nacos 的配置处理
Nacos 是基于 ApplicationContextInitializer 完成的属性查询 ,ApplicationContextInitializer 是在容器初始化 (prepareContext
),Bean加载前执行 ,我来简单写个 Demo :
java
@Slf4j
public class CustomPropertySource implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 从容器体系中获取现有的 environment
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 创建一个属性源 , 从 Nacos 中拉取数据
Map<String, Object> sourceMap = new HashMap<>();
// 把拉取的数据写入对应的 Map
MapPropertySource propertySource = new MapPropertySource("mySource", sourceMap);
// 丢到 environment 中
environment.getPropertySources().addFirst(propertySource);
}
}
而对应 Nacos 的几个类为 :
- S1 : PropertySourceBootstrapConfiguration 中触发
initialize
操作 - S2 : 从 NacosPropertySourceLocator #
locate
中发起配置的查询和处理 - S3 : 通过 NacosConfigService 拉取配置,最终通过 Builder 构建 PropertySource
- S4 : 通过
loadApplicationConfiguration
👉loadNacosDataIfPresent
👉addFirstPropertySource
一步步的把配置写到环境中
总结 ❗❗❗主流程很简单 :拉取Nacos配置 👉 构建 PropertySource 👉 写入Enviroment
细节问题一 : Nacos 会拉取哪几份配置,基于什么规则?
一般从启动命令中就能看到,Nacos Client 在这个加载了很多的配置文件。
总结一下就是会依次组合几种配置文件(properties 以及 .yml .yaml),都去查询 :
- 在 locate 中拿到配置前缀,如果没配置会取 env.getProperty("spring.application.name")
- 然后依次组装后缀 和 profile 多次查询
细节问题二 : 关于 Nacos 里面的多配置和优先级
具体原理看这一篇 : juejin.cn/post/701818... , 直接结论如下 :
java
- application 主配置 > extensionConfigs > sharedConfigs
- extensionConfigs/sharedConfigs 排在后面的数组比前面的优先级高
- application 主逻辑 profile > 带后缀
3.2 配置的注入
Spring 的属性注入是在 AbstractAutowireCapableBeanFactory # populateBean
环节完成 ,最核心的一张处理代码就是 :
java
// 循环不同的 BeanPostProcessors
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 通过对应的 BeanPostProcessor 完成属性和Bean的注入
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
}
}
👉 简单说一下这个环节的核心流程 :
- S1 : 当一个 Bean 执行到
populateBean
环节后 ,会循环当前支持的所有BeanPostProcessor
处理类 - S2 : 通过
findAutowiringMetadata
方法拿到当前处理类能处理的元数据集合。 - S3 : 通过
element.inject
放射写入对应的对象 , 最终通过 invoke 实现
总结 ❗❗❗ 进入 populateBean 👉 调用 inject 👉 拿到 PropertySource 👉 反射注入
细节问题一 : Bean 注入和 Value 注入分别怎么处理的
这两种注解标注的对象分别是在多个 BeanPostProcessor 中进行处理的,通常执行的是 CommonAnnotationBeanPostProcessor 和 AutowiredAnnotationBeanPostProcessor 。
其中 CommonAnnotationBeanPostProcessor 主要负责 @Resource、@PostConstruct、@PreDestroy
等JAVA 老注解。 而 AutowiredAnnotationBeanPostProcessor 主要用于处理 @Autowired 和 @Value
注解。
在每个 BeanPostProcessor 中都会有个 buildResourceMetadata
方法用于生成 InjectionMetadata , 该对象就包含当前处理类能注入的对象列表。
所以,整个注入环节不是一次就完成,会分别执行很多次。
细节问题二 : 注入的值是从哪里取到的?
最终数据肯定都是 enviroment 中拿到的,只不过流程要理清楚,总结起来就是 :
- S1 : 在 AutowiredAnnotationBeanPostProcessor #
inject
环节开始进行属性的反射 - S2 : 通过 BeanFactory 的
resolveDependency
拿到 Bean 关系和关联的Bean - S3 : 因为 Value 通常都是使用$进行的嵌入式注入,所以会通过 resolveEmbeddedValue 解析出值
- S4 : 最后通过 PlaceholderResolvingStringValueResolver 进行值的解析
❓为什么 PlaceholderResolvingStringValueResolver 中可以拿到值?
PropertySourcesPropertyResolver 中包含了一个 PropertySources 对象 , 其中包含了所有的 Enviroment 信息
细节问题三 : 不同类型的数据是怎么注入的?
最直接的问题就是, 对于数组,Map ,List 应该怎么处理 :
同样是在 doResolveDependency 环节中,所有会获取到当前属性的 type -> descriptor.getDependencyType() , 此时会返回对应注入属性的 Class 类型
此时当获取到对应的类型后,则可以通过 TypeConverter 进行数据转换 ,核心就2段代码 :
java
// 获取类型转换类
Class<?> type = descriptor.getDependencyType();
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
// 转换数据类型
converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
另外这里需要注意一下 , @Value 对复杂格式的接收不太理想
总结
- Nacos 数据扫描部分 : 拉取Nacos配置 👉 构建 PropertySource 👉 写入Enviroment
- 属性载入部分 : 进入 populateBean 👉 调用 inject 👉 拿到 PropertySource 👉 反射注入
关于 Bean 加载和 Configuration 就不在这一篇讲了.
另外更细致的可以看这几篇 :