面试聊到了 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配置的读取流程

相关推荐
颜酱41 分钟前
07 | 把字段与指标同步到 Qdrant(生成阶段)
前端·人工智能·后端
Larcher1 小时前
从“加载模型”界面到端侧推理:拆解一个 React + WebGPU 大模型 Demo
javascript·后端
极客先躯1 小时前
高级java每日一道面试题-2026年05月03日-实战篇[Docker]-如何实现容器化环境的数据加密?
java·运维·docker·容器·金融·加解密·高级面试
xqqxqxxq2 小时前
Java Socket 多人聊天室(私聊+群聊)技术笔记(V4版本)
java·网络·笔记
Larcher2 小时前
从状态快照到惰性初始化:读懂 React useState 的三个关键场景
javascript·人工智能·后端
lazy H2 小时前
Git clone 怎么用?克隆项目及常见问题完整教程
大数据·git·后端·学习·搜索引擎·github
wang09072 小时前
自己动手写一个spring之aop_1
java·后端·spring
神奇小汤圆2 小时前
IDEA 运行报 Command line is too long?别慌,两招搞定(附原理)
后端
SelectDB2 小时前
Apache Doris 4.1 全面增强 Iceberg:支持 UPDATE、MERGE INTO 与 Iceberg V3
后端
国科安芯2 小时前
FreeRTOS RISC-V 浮点上下文切换移植:在 IAR 工程中完整保存 FPU 寄存器
java·开发语言·单片机·嵌入式硬件·算法·系统架构·risc-v