【深入理解SpringCloud微服务】深入理解nacos配置中心(二)——客户端启动源码分析
【深入理解SpringCloud微服务】深入理解nacos配置中心(二)——客户端启动源码分析
- 服务端启动原理回顾
- 源码解析
- SpringBoot如何调用到PropertySourceLocator的locate()方法
- NacosPropertySourceLocator#locate()
- NacosPropertySourceLocator#loadNacosDataIfPresent()
- NacosPropertySourceBuilder#build()
- NacosConfigService#getConfig()
服务端启动原理回顾
在上一篇的《宏观理解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();// 遍历所有的PropertySourceLocatorfor (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 {...@Beanpublic NacosPropertySourceLocator nacosPropertySourceLocator(NacosConfigProperties nacosConfigProperties) {return new NacosPropertySourceLocator(nacosConfigProperties);}}
由于nacos向Spring注册了自己的PropertySourceLocator——NacosPropertySourceLocator,那么自然就会调用到NacosPropertySourceLocator的locate方法。
@Overridepublic 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方法获取配置信息,返回NacosPropertySourceNacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,fileExtension, isRefreshable);// NacosPropertySource放入CompositePropertySourcethis.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);...// 获取到的配置信息转成MapMap<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()方法
@Overridepublic 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配置中心客户端启动源码的整体流程,下面是源码分析的整体流程图: