前言
之前写了部分 Nacos 注册中心的源码分析文章,现在继续接着看 Nacos 配置中心的源码了。 本次目标有两个:
- 了解 Spring Boot 项目启动时,如何从 Nacos 配置中心获取初始化配置。
- Nacos 配置中心配置变更时,Spring Boot 项目如何更新。
当然实践至上,当弄明白上述两个问题后,也需要写一个丐版注册中心来加强认知。
丐版注册中心需要实现两个功能:
- Spring Boot 项目启动时,可从丐版注册中心获取初始配置。
- 丐版注册中心配置变更时,Spring Boot 项目能更新配置。
Spring Boot 初始化器
我们在《 『深入学习 Spring Boot』(二) 系统初始化器 》中曾学习到:
官方描述:系统初始化器是Spring容器刷新之前执行的一个回调函数。
作用是向Spring Boot容器中注册属性,需要实现
ApplicationContextInitializer
接口。
Spring Cloud 配置中心就是依靠ApplicationContextInitializer
去调用远程接口,获取配置信息。
org.springframework.cloud.bootstrap.config
包下,有此类:
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
// ......
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
// 从应用上下文中,取出环境类
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
// 执行 locateCollection 方法,获取具体配置。
// 注意本方法执行后,配置已经被从远程加载进来了。
Collection<PropertySource<?>> source = locator.locateCollection(environment);
// ....
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
// ....
}
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
// ....
// 把 composite(远程的属性) 设置到 环境类中
insertPropertySources(propertySources, composite);
// ....
}
}
// .....
}
这里还涉及到了,Spring 上下文中的Environment
类。
如果觉得陌生,可先阅读 《『深入学习 Spring Boot』(十) Environment 》、《『深入学习 Spring Boot』(十一) profiles 》我的两篇博客。
此类就是 Spring Boot 与 Nacos 之间的 God Class
了。
PropertySourceLocator 属性加载器
这是一个接口定义,其实现类将真正读取属性。
public interface PropertySourceLocator {
PropertySource<?> locate(Environment environment);
default Collection<PropertySource<?>> locateCollection(Environment environment) {
return locateCollection(this, environment);
}
// 注意,上文 PropertySourceBootstrapConfiguration # initialize,调用了此方法。
// 也就是调用了 locate 方法。
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
Environment environment) {
PropertySource<?> propertySource = locator.locate(environment);
// ....
}
}
查看此接口的关系:
好巧不巧,有个 Nacos 相关的实现。这下子可以串起来了。
Spring Boot 项目启动时,将执行 PropertySourceBootstrapConfiguration # initialize()
,在其中获取到 PropertySourceLocator
的实现类遍历执行locate()
方法。
那么我们可以有一个猜想了,NacosPropertySourceLocator
在locate()
中,请求配置中心获取到配置,设置到Environment
中,Spring Boot 不就可以正常启动了。
NacosPropertySourceLocator
public class NacosPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment env) {
// ....
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
// 1.加载共享配置
loadSharedConfiguration(composite);
// 2.加载扩展配置
loadExtConfiguration(composite);
// 3.加载应用配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
// 1、2、3底层都是通过调用 loadNacosDataIfPresent
private void loadNacosDataIfPresent(final CompositePropertySource composite,
final String dataId, final String group, String fileExtension,
boolean isRefreshable) {
// ....
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
fileExtension, isRefreshable);
this.addFirstPropertySource(composite, propertySource, false);
}
// 继续调用 loadNacosPropertySource()
private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
// 不自动刷新,直接从本地缓存拿
if (NacosContextRefresher.getRefreshCount() != 0) {
if (!isRefreshable) {
return NacosPropertySourceRepository.getNacosPropertySource(dataId,
group);
}
}
// 这里是发请求的方法
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
}
NacosPropertySourceBuilder
public class NacosPropertySourceBuilder {
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
Map<String, Object> p = loadNacosData(dataId, group, fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
p, new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}
private Map<String, Object> loadNacosData(String dataId, String group,
String fileExtension) {
String data = null;
try {
// 获取配置。重点。
data = configService.getConfig(dataId, group, timeout);
// 格式化配置
Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
.parseNacosData(data, fileExtension);
return dataMap == null ? EMPTY_MAP : dataMap;
}
return EMPTY_MAP;
}
}
NacosConfigService
public class NacosConfigService implements ConfigService {
@Override
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
return getConfigInner(namespace, dataId, group, timeoutMs);
}
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
group = blank2defaultGroup(group);
ParamUtils.checkKeyParam(dataId, group);
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group);
// ...
try {
// 从远程获取配置信息。content 字符串的内容就是具体的配置内容。
ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
cr.setContent(response.getContent());
cr.setEncryptedDataKey(response.getEncryptedDataKey());
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
} catch (NacosException ioe) {
// ...
}
// ...
}
}
从远程获取到配置项后,就可以保存到上下文环境中,进行应用程序的启动了。
小结
Nacos 配置中心是如何与 Spring Cloud 结合的?
- Spring Boot 有初始化器机制,专门用于初始化属性的。Spring Cloud 实现了一个初始化器,并在其中调用
PropertySourceLocator#locate()
。 - Nacos 有一
PropertySourceLocator
的实现类,并在locate()
方法中进行远程调用,获取具体配置项。