面试聊到了 Nacos 客户端配置加载过程,怎么回答才能拿高分

👈👈👈 欢迎点赞收藏关注哟

首先分享之前的所有文章 >>>> 😜😜😜
文章合集 : 🎁 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 中进行处理的,通常执行的是 CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor

其中 CommonAnnotationBeanPostProcessor 主要负责 @Resource、@PostConstruct、@PreDestroy 等JAVA 老注解。 而 AutowiredAnnotationBeanPostProcessor 主要用于处理 @Autowired 和 @Value 注解。

在每个 BeanPostProcessor 中都会有个 buildResourceMetadata 方法用于生成 InjectionMetadata , 该对象就包含当前处理类能注入的对象列表。

所以,整个注入环节不是一次就完成,会分别执行很多次。

细节问题二 : 注入的值是从哪里取到的?

最终数据肯定都是 enviroment 中拿到的,只不过流程要理清楚,总结起来就是 :

  • S1 : 在 AutowiredAnnotationBeanPostProcessor # inject 环节开始进行属性的反射
  • S2 : 通过 BeanFactoryresolveDependency 拿到 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 就不在这一篇讲了.

另外更细致的可以看这几篇 :

盘点 SpringIOC : Bean 创建之属性注入

盘点 SpringBoot : Application配置的读取流程

相关推荐
程序猿追42 分钟前
我搭了个网页工具:输入关键词,SERP API 自动吐出比价 Excel
后端
Lee川44 分钟前
RAG 实战:从一篇掘金文章出发,拆解检索增强生成的全链路
前端·人工智能·后端
Lee川1 小时前
MCP 高德地图实战:当 AI 学会使用工具,一个协议如何重塑大模型的行动边界
前端·人工智能·后端
楼田莉子1 小时前
C++17新特性:__had_include/属性/求值顺序规则
开发语言·c++·后端
kyriewen1 小时前
用魔法打败魔法:我让AI替我去面试前端岗,AI面试官给我打了92分,还发了offer
前端·javascript·面试
程序员cxuan1 小时前
Codex 把我家烂网给优化后,我 TM 直接原地起飞了。
人工智能·后端·程序员
IT_陈寒1 小时前
Redis批量删除踩了坑,原来DEL命令不是万能的
前端·人工智能·后端
500842 小时前
昇腾 CANN 的五层架构,到底分了哪五层
java·人工智能·分布式·架构·ocr·wpf
摇滚侠2 小时前
Java 零基础全套教程,File 类与 IO 流,笔记 177-178
java·开发语言·笔记
叫我少年2 小时前
C# 命名空间与 using 指令 — 文件范围、全局导入、别名
后端