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

相关推荐
Query*5 小时前
杭州2024.08 Java开发岗面试题分类整理【附面试技巧】
java·开发语言·面试
WZTTMoon5 小时前
Spring Boot 4.0 迁移核心注意点总结
java·spring boot·后端
寻kiki5 小时前
scala 函数类?
后端
疯狂的程序猴5 小时前
iOS App 混淆的真实世界指南,从构建到成品 IPA 的安全链路重塑
后端
旷野说5 小时前
为什么 MyBatis 原生二级缓存“难以修复”?
java·java-ee·mybatis
8***23555 小时前
【wiki知识库】07.用户管理后端SpringBoot部分
java
bcbnb5 小时前
iOS 性能测试的工程化方法,构建从底层诊断到真机监控的多工具测试体系
后端
开心就好20255 小时前
iOS 上架 TestFlight 的真实流程复盘 从构建、上传到审核的团队协作方式
后端
小周在成长6 小时前
Java 泛型支持的类型
后端
aiopencode6 小时前
Charles 抓不到包怎么办?HTTPS 抓包失败、TCP 数据流异常与底层补抓方案全解析
后端