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

相关推荐
松仔log29 分钟前
JetPack——Paging3+Room
android·java·zoom
㳺三才人子6 小时前
初探 Flask
后端·python·flask·html
星栈独行6 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下6 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.6 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易6 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0077 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶7 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
Raink老师7 小时前
【AI面试临阵磨枪-62】设计基于 RAG 的内部知识库问答平台(多租户、权限、文件上传、实时更新)
人工智能·面试·职场和发展
ltl7 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端