当前位置: 首页 > news >正文

Nacos配置文件如何初始化的

https://www.bilibili.com/video/BV1Zd4UzpE1X


文章目录

    • 一、如何加载Nacos配置到容器里
      • 1、初始化加载
      • 2、解析data-id数据
    • 二、解析
      • 1、解析的入口
      • 2、解析的开始
      • 3、一个特殊说明
      • 4、循环解析
      • 5、解析
    • 三、总结

一、如何加载Nacos配置到容器里


1、初始化加载


Nacos 自定义了一个初始化的类 NacosConfigApplicationContextInitializer,继承关系如下

public class NacosConfigApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {}

SpringBoot在初始化的时候就会循环调用 ApplicationContextInitializer 的 initialize 方法

protected void applyInitializers(ConfigurableApplicationContext context) {for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");initializer.initialize(context);}
}

2、解析data-id数据


在 initialize 方法里会通过配置的 data-id 去请求Nacos获取所有的配置,并加载到容器中去

com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer#initialize

public void initialize(ConfigurableApplicationContext context) {singleton.setApplicationContext(context);environment = context.getEnvironment();nacosConfigProperties = NacosConfigPropertiesUtils.buildNacosConfigProperties(environment);final NacosConfigLoader configLoader = NacosConfigLoaderFactory.getSingleton(nacosConfigProperties, environment, builder);if (!enable()) {logger.info("[Nacos Config Boot] : The preload configuration is not enabled");}else {if (processor.enable()) {processor.publishDeferService(context);configLoader.addListenerIfAutoRefreshed(processor.getDeferPropertySources());}else {// 加载配置configLoader.loadConfig();configLoader.addListenerIfAutoRefreshed();}}// ....
}

加载的过程就是通过data-id,去请求服务端获取数据,生成NacosPropertySource,然后存入 mutablePropertySources。后面解析数据的时候就会从 NacosPropertySource里面去进行匹配

com.alibaba.boot.nacos.config.util.NacosConfigLoader#loadConfig

public void loadConfig() {// 获取全局的配置文件MutablePropertySources mutablePropertySources = environment.getPropertySources();// 去请求Nacos服务端获取数据List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties, nacosConfigProperties.getType());for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {List<NacosPropertySource> elements = reqSubNacosConfig(config, globalProperties, config.getType());sources.addAll(elements);}if (nacosConfigProperties.isRemoteFirst()) {for (ListIterator<NacosPropertySource> itr = sources.listIterator(sources.size()); itr.hasPrevious();) {mutablePropertySources.addAfter( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());}} else {// 走这里...,把数据存入到 mutablePropertySourcesfor (NacosPropertySource propertySource : sources) {mutablePropertySources.addLast(propertySource);}}
}

eg某一个 propertySource 的情况

在这里插入图片描述

二、解析


上面已经知道了如何去加载Nacos的配置到容器里面,再来看看某一个字段如何去解析的

问题:如果有两个data-id, dataIdA和dataIdB,里面的内容如下。在dataIdB里面引用了dataIdA的变量,又是如何解析的呢?

# dataIdA
servicePort: 8080# dataIdB
server:port: ${servicePort}# Java使用
@Value("${server.port}")

1、解析的入口


在SpringBoot里面有这样一个后置处理器 AutowiredAnnotationBeanPostProcessor, 它专门用来解析 @Autowired@Value 注解。读取配置文件用的是 @Value注解 (关于 @NacosValue 的解析,下篇再说)

postProcessMergedBeanDefinition 会遍历每一个bean,看看是否需要进行处理

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);
}

找到合适的bean构建 InjectionMetadata,然后存入 injectionMetadataCache 中

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {// Fall back to class name as cache key, for backwards compatibility with custom callers.String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// Quick check on the concurrent map first, with minimal locking.InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {metadata.clear(pvs);}metadata = buildAutowiringMetadata(clazz);this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;
}

在这里插入图片描述


buildAutowiringMetadata 的方法可以自行去看,下面给出头部截图,其实就是判断 class里面是否有这两个注解。 最终生成的是 AutowiredFieldElement对象


在后置处理器中,会遍历每一个bean,如果找到合适的就会进行 inject (解析并设置值),刚刚在 postProcessMergedBeanDefinition 已经符合的bean找到了

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;
}

2、解析的开始


前面说了生成的是 AutowiredFieldElement对象,所以执行的inject 就是

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;if (this.cached) {// ....}else {value = resolveFieldValue(field, bean, beanName);}// 进行赋值if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}
}

resolveFieldValue 方法之后就是一步步进行解析,在到真正的解析方法之前,还有很多跳转,这里把路径列出来

  1. org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue
  2. org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
  3. org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
  4. org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue

3、一个特殊说明

resolveEmbeddedValue 方法的逻辑如下,最开始我一直没找到 resolver.resolveStringValue 执行的到底是哪里

org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue

@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {if (value == null) {return null;}String result = value;for (StringValueResolver resolver : this.embeddedValueResolvers) {result = resolver.resolveStringValue(result);if (result == null) {return null;}}return result;
}

最后发现,resolver.resolveStringValue(result) 执行的是一个匿名类,也就是下面这个代码快

org.springframework.context.support.PropertySourcesPlaceholderConfigurer#processProperties(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, org.springframework.core.env.ConfigurablePropertyResolver)

在这里插入图片描述


走到匿名类之后,还有一些路径,这里继续给出

  1. org.springframework.core.env.AbstractPropertyResolver#resolveRequiredPlaceholders
  2. org.springframework.core.env.AbstractPropertyResolver#doResolvePlaceholders
  3. org.springframework.util.PropertyPlaceholderHelper#replacePlaceholders(java.lang.String, org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver)
  4. org.springframework.util.PropertyPlaceholderHelper#parseStringValue

4、循环解析


parseStringValue 会去循环解析,再看看上面的问题

  1. 最开始得到 ${server.port} > server.port
  2. 拿 server.port 匹配数据到 ${servicePort} > servicePort
  3. 拿 servicePort 匹配到 8080

org.springframework.util.PropertyPlaceholderHelper#parseStringValue

protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {int startIndex = value.indexOf(this.placeholderPrefix);if (startIndex == -1) {return value;}StringBuilder result = new StringBuilder(value);while (startIndex != -1) {int endIndex = findPlaceholderEndIndex(result, startIndex);if (endIndex != -1) {String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);String originalPlaceholder = placeholder;if (visitedPlaceholders == null) {visitedPlaceholders = new HashSet<>(4);}if (!visitedPlaceholders.add(originalPlaceholder)) {throw new IllegalArgumentException("Circular placeholder reference '" + originalPlaceholder + "' in property definitions");}// 递归调用解析placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);String propVal = placeholderResolver.resolvePlaceholder(placeholder);if (propVal == null && this.valueSeparator != null) {int separatorIndex = placeholder.indexOf(this.valueSeparator);if (separatorIndex != -1) {String actualPlaceholder = placeholder.substring(0, separatorIndex);String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);if (propVal == null) {propVal = defaultValue;}}}if (propVal != null) {// Recursive invocation, parsing placeholders contained in the// previously resolved placeholder value.propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);if (logger.isTraceEnabled()) {logger.trace("Resolved placeholder '" + placeholder + "'");}startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());}else if (this.ignoreUnresolvablePlaceholders) {// Proceed with unprocessed value.startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());}else {throw new IllegalArgumentException("Could not resolve placeholder '" +placeholder + "'" + " in value \"" + value + "\"");}visitedPlaceholders.remove(originalPlaceholder);}else {startIndex = -1;}}return result.toString();
}

5、解析


得到某个key的时候,就会调用这个方法来从配置文件得到具体的值

org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver#findPropertyValue

private Object findPropertyValue(String key) {// attached 里面存放了第一步加载进去的 source配置ConfigurationPropertySourcesPropertySource attached = getAttached();if (attached != null) {ConfigurationPropertyName name = ConfigurationPropertyName.of(key, true);if (name != null) {try {// 里面会 for循环每一个 sourceConfigurationProperty configurationProperty = attached.findConfigurationProperty(name);return (configurationProperty != null) ? configurationProperty.getValue() : null;}catch (Exception ex) {}}}return this.defaultResolver.getProperty(key, Object.class, false);
}
ConfigurationProperty findConfigurationProperty(ConfigurationPropertyName name) {if (name == null) {return null;}for (ConfigurationPropertySource configurationPropertySource : getSource()) {ConfigurationProperty configurationProperty = configurationPropertySource.getConfigurationProperty(name);if (configurationProperty != null) {return configurationProperty;}}return null;
}

getPropertySource 获取就是在第一步添加进去的 NacosPropertySource

@Override
public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) {if (name == null) {return null;}for (PropertyMapper mapper : this.mappers) {try {for (String candidate : mapper.map(name)) {Object value = getPropertySource().getProperty(candidate);if (value != null) {Origin origin = PropertySourceOrigin.get(this.propertySource, candidate);return ConfigurationProperty.of(this, name, value, origin);}}}catch (Exception ex) {}}return null;
}

上面是用@Value注解,用 NacosValue也是一样的,只不过后置处理器变成了 NacosValueAnnotationBeanPostProcessor


三、总结

  1. Nacos 有一个初始化的类 NacosConfigApplicationContextInitializer,继承关系 NacosConfigApplicationContextInitializer implements ApplicationContextInitializer,在 initialize 方法里会通过配置的 data-id 去请求Nacos获取所有的配置,并加载到容器中去
  2. AutowiredAnnotationBeanPostProcessor 会找到每一个使用到 @Value的地方,然后去匹配配置文件中的数据,进行解析(通过上一步已经把全部的data-id都加载到内存了)
    1. ${server.port} > server.port
    2. server.port 匹配数据到 ${servicePort} > servicePort
    3. servicePort 匹配到 8080
http://www.dtcms.com/a/487380.html

相关文章:

  • 创维E900V22D刷入armbian hdmi开机花屏和网络无法使用 解决方式【亲测】
  • 全面解答:远控快捷键切换方便吗?不同系统间键盘映射设置随需调
  • 互联网门户网站有哪些鞍山市网站建设
  • 【vLLM】源码解读:vllm如何识别到第三方自定义设备的
  • 网站制作的基本成都网站建设网络
  • 日志系统4 日志类型的设计
  • 深度学习:从图片数据到模型训练(十分类)
  • PCB mark点
  • 大兴网站开发网站建设咨询100%能上热门的文案
  • 网站建设douyanet杭州专业建设网站哪里好
  • Mysql杂志(三十二)——redo日志、undo日志
  • 2017做那些网站致富网博士自助建站系统
  • 上海做公司网站的公司室内设计网站模板
  • 化州 网站建设如何自己建营销网站
  • Kafka-2 Docker 部署单节点环境(SpringBoot验证)
  • 从0开始了解kafka《第二篇 kafka的安装、管理和配置》
  • 02 SQL数据检索入门 - SELECT语句详解
  • 从分词器构建到强化学习:nanochat开源项目下载与部署全流程教程,教你一步步训练ChatGPT语言模型
  • 长安镇仿做网站注册网站好的平台
  • 加强公司内部网站建设正邦设计公司
  • 巩义网站建设价格怎么注册个人工作室
  • 四川网站建设益友网站地图怎么用
  • 制作一个响应式网站开发工具景观设计公司名称
  • 网站设计网站开发优化欢迎你的加入
  • MySql 基本操作指令大全
  • 军用网站建设ui设计一个月挣多少钱
  • 海宁市住房和城乡建设网站网站源码程序修改
  • 做足球预测的网站小程序开发教程视频
  • 消息队列相关知识总结
  • Kafka集群Broker一点通