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

相关推荐
lcreek1 小时前
Java 反序列化漏洞深度解析(一):从URLDNS到真正的DNS探测
java·反序列化漏洞
Raink老师2 小时前
【AI面试临阵磨枪-70】Agent 系统如何做分布式调度、跨服务协作、故障恢复?
人工智能·面试·职场和发展
杰克尼2 小时前
天机学堂复习总结(day03-day04)
java·开发语言·redis·elasticsearch·spring cloud
Raink老师2 小时前
【AI面试临阵磨枪-71】如何用 AI 优化推荐系统、内容审核、广告创意、搜索体验?
人工智能·面试·职场和发展
x***r1512 小时前
jdk-11.0.16.1_windows使用步骤详解(附JDK 11环境变量配置与验证教程)
java·开发语言·windows
Raink老师2 小时前
【AI面试临阵磨枪-72】电商全场景 AI Agent 设计(商品咨询 / 订单 / 物流 / 售后 / 退款)
人工智能·面试·职场和发展
弹简特3 小时前
【Java项目-轻聊】01-项目演示+项目介绍+准备工作+项目源码
java
luck_bor3 小时前
File类&递归作业
java·开发语言
武子康3 小时前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
花椒技术4 小时前
企业内部 Agent 落地复盘:Gateway、Skill 和二次确认如何串起受控业务执行
后端·agent·ai编程