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 方法之后就是一步步进行解析,在到真正的解析方法之前,还有很多跳转,这里把路径列出来
- org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue
- org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
- org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
- 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)
走到匿名类之后,还有一些路径,这里继续给出
- org.springframework.core.env.AbstractPropertyResolver#resolveRequiredPlaceholders
- org.springframework.core.env.AbstractPropertyResolver#doResolvePlaceholders
- org.springframework.util.PropertyPlaceholderHelper#replacePlaceholders(java.lang.String, org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver)
- org.springframework.util.PropertyPlaceholderHelper#parseStringValue
4、循环解析
parseStringValue
会去循环解析,再看看上面的问题
- 最开始得到 ${server.port} > server.port
- 拿 server.port 匹配数据到 ${servicePort} > servicePort
- 拿 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
三、总结
- Nacos 有一个初始化的类 NacosConfigApplicationContextInitializer,继承关系
NacosConfigApplicationContextInitializer implements ApplicationContextInitializer
,在 initialize 方法里会通过配置的 data-id 去请求Nacos获取所有的配置,并加载到容器中去 - AutowiredAnnotationBeanPostProcessor 会找到每一个使用到
@Value
的地方,然后去匹配配置文件中的数据,进行解析(通过上一步已经把全部的data-id都加载到内存了)- ${server.port} > server.port
- server.port 匹配数据到 ${servicePort} > servicePort
- servicePort 匹配到 8080