【深入理解SpringCloud微服务】深入理解nacos配置中心(二)——客户端启动源码分析
服务端启动原理回顾
在上一篇的《宏观理解nacos配置中心原理》的文章中,我们提到SpringBoot在启动之后会调用PropertySourceLocator的locate()方法获取配置信息。
PropertySourceLocator的locate()方法是用于获取配置信息,具体如何获取由实现这个接口的实现类去实现。可以通过RPC从远程获取,或者读取本地某个文件,也可以查询数据库,但是这个接口规定返回的是PropertySource对象。
于是nacos就实现了一个NacosPropertySourceLocator。
NacosPropertySourceLocator的locate()方法里面通过NacosConfigService向nacos服务端发起RPC远程调用获取配置信息,然后把获取到的配置信息放入CompositePropertySource对象返回。
源码解析
SpringBoot如何调用到PropertySourceLocator的locate()方法
PropertySourceLocator的locate()方法是从prepareContext()方法调用进去的,我们现在SpringApplication的run()方法中找到prepareContext()方法。
public ConfigurableApplicationContext run(String... args) {
...
try {
...
context = createApplicationContext();
...
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
...
}
catch (...) {...}
...
return context;
}
prepareContext()就在refreshContext()方法前调用。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
...
applyInitializers(context);
...
}
prepareContext方法中会把Environment对象设置到Spring容器中,然后调用applyInitializers方法。
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
...
initializer.initialize(context);
}
}
applyInitializers方法会获取所有的ApplicationContextInitializer,这些ApplicationContextInitializer都是在SpringApplication初始化的时候加载的。
这里就会调用到PropertySourceBootstrapConfiguration#initialize()方法,里面就会调用到PropertySourceLocator的locate方法。
public void initialize(ConfigurableApplicationContext applicationContext) {
// 创建一个CompositePropertySource对象
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME);
...
// 从Spring容器中获取environment对象
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 遍历所有的PropertySourceLocator
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = null;
// 调用PropertySourceLocator的locate方法获取配置信息
// 返回PropertySource对象
source = locator.locate(environment);
...
// PropertySource放入到CompositePropertySource中
composite.addPropertySource(source);
empty = false;
}
if (!empty) {
// 获取到environment对象中的MutablePropertySources对象
MutablePropertySources propertySources = environment.getPropertySources();
...
// 把CompositePropertySource放入MutablePropertySources对象中
insertPropertySources(propertySources, composite);
...
}
}
首先创建一个CompositePropertySource对象,用于存放每个PropertySourceLocator的locate方法返回的PropertySource对象。
然后循环遍历每一个PropertySourceLocator,调用locate方法,返回的PropertySource对象放入CompositePropertySource中。
最后从environment对象中取出MutablePropertySources对象,把CompositePropertySource放入MutablePropertySources中。
NacosPropertySourceLocator#locate()
nacos在spring-cloud-alibaba-nacos-config中的spring.factories文件中指定了自己的配置类NacosConfigBootstrapConfiguration,NacosConfigBootstrapConfiguration中通过@Bean注解配置了NacosPropertySourceLocator。
@Configuration
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {
...
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator(
NacosConfigProperties nacosConfigProperties) {
return new NacosPropertySourceLocator(nacosConfigProperties);
}
}
由于nacos向Spring注册了自己的PropertySourceLocator——NacosPropertySourceLocator,那么自然就会调用到NacosPropertySourceLocator的locate方法。
@Override
public PropertySource<?> locate(Environment env) {
...
// 创建一个CompositePropertySource对象
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
// 加载共享配置
loadSharedConfiguration(composite);
// 加载扩展配置
loadExtConfiguration(composite);
// 加载应用程序配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
// 返回CompositePropertySource对象
return composite;
}
我们知道nacos是有共享配置、扩展配置和应用程序配置之分的,这里就是分别加载共享配置、扩展配置和应用程序配置,越往后配置的优先级越高。
这三个方法,都会调用到loadNacosDataIfPresent()方法去获取配置。
NacosPropertySourceLocator#loadNacosDataIfPresent()
private void loadNacosDataIfPresent(final CompositePropertySource composite,
...
// 调用loadNacosPropertySource方法获取配置信息,返回NacosPropertySource
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
fileExtension, isRefreshable);
// NacosPropertySource放入CompositePropertySource
this.addFirstPropertySource(composite, propertySource, false);
}
NacosPropertySourceLocator的loadNacosDataIfPresent()调用loadNacosPropertySource方法获取配置信息,返回NacosPropertySource,然后把NacosPropertySource放入CompositePropertySource。
private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
...
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
loadNacosPropertySource方法调用NacosPropertySourceBuilder的build方法。
NacosPropertySourceBuilder#build()
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, ...);
...
return nacosPropertySource;
}
NacosPropertySourceBuilder的build方法中调用loadNacosData方法获取配置信息,loadNacosData方法返回的是一个Map结构,然后把Map封装成NacosPropertySource返回。
private Map<String, Object> loadNacosData(String dataId, String group,
String fileExtension) {
String data = null;
try {
// 调用NacosConfigService获取配置信息
data = configService.getConfig(dataId, group, timeout);
...
// 获取到的配置信息转成Map
Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
.parseNacosData(data, fileExtension);
return dataMap == null ? EMPTY_MAP : dataMap;
}
...
}
在NacosPropertySourceBuilder的loadNacosData方法获中,调用NacosConfigService的getConfig方法获取配置信息,然后把返回的配置信息转成Map结构,最后返回该Map。
NacosConfigService#getConfig()
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
return getConfigInner(namespace, dataId, group, timeoutMs);
}
NacosConfigService的getConfig()调用getConfigInner方法。
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
...
// 优先从本地配置文件获取
String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
if (content != null) {
...
return content;
}
try {
// 本地没有,再调用ClientWorker从远程服务器获取
ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
cr.setContent(response.getContent());
...
content = cr.getContent();
return content;
} catch (...) {...}
...
}
NacosConfigService的getConfigInner方法首先从本地配置文件获取,如果本地没有才调用ClientWorker的getServerConfig从远程服务器获取。
然后ClientWorker的getServerConfig方法会调用到ConfigRpcTransportClient#queryConfig()方法
@Override
public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeouts, boolean notify)
throws NacosException {
// 创建ConfigQueryRequest类型的请求对象
ConfigQueryRequest request = ConfigQueryRequest.build(dataId, group, tenant);
...
// 创建RpcClient对象,用于发起远程调用
RpcClient rpcClient = getOneRunningClient();
...
// 发起GRPC远程调用
ConfigQueryResponse response = (ConfigQueryResponse) requestProxy(rpcClient, request, readTimeouts);
...
if (response.isSuccess()) {
// 保存到本地配置文件
LocalConfigInfoProcessor.saveSnapshot(this.getName(), dataId, group, tenant, response.getContent());
...
return configResponse;
}
...
}
ConfigRpcTransportClient的queryConfig方法发起GRPC远程调用请求远程服务器获取配置内容,然后把获取到的配置内容保存到本地配置文件,那么下次再次获取时,就不需要发起远程调用请求从远程服务器获取了,而是直接取本地配置文件。
private Response requestProxy(RpcClient rpcClientInner, Request request, long timeoutMills)
throws NacosException {
...
// 调用RpcClient的request方法,发起GRPC远程调用
return rpcClientInner.request(request, timeoutMills);
}
requestProxy方法里面就是调用调用RpcClient的request方法,发起GRPC远程调用,里面依赖到了grpc的API,我们就不往下看grpc的逻辑了。
以上便是nacos配置中心客户端启动源码的整体流程,下面是源码分析的整体流程图: