我們從SpringBoot的run方法入手,主體邏輯步驟為:
1.prepareEnvironment
2.createApplicationContext
3.prepareContext
4.refreshContext
5.afterRefresh
prepareEnvironment
org.springframework.boot.SpringApplication#run(java.lang.String...) =》org.springframework.boot.SpringApplication#prepareEnvironment =》org.springframework.boot.SpringApplicationRunListeners#environmentPrepared =》org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared
在這里通過org.springframework.context.event.SimpleApplicationEventMulticaster廣播ApplicationEnvironmentPreparedEvent事件。
那么,事件的監聽者有哪些?通過代碼走讀發現,事件的監聽者在EventPublishingRunListener構造方法中通過SpringApplication.getListeners()獲取。那么,SpringApplication的監聽者又是哪來的?
同樣,走讀代碼如下,通過springfactories機制加載org.springframework.context.ApplicationListener實現:

這其中就有一個Spring Cloud擴展的實現:

這個listener監聽的正是上文提到的環境准備事件ApplicationEnvironmentPreparedEvent:

org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent主要通過構建臨時ApplicationContext,利用Spring自帶的IOC能力,查找以SpringFactories方式擴展org.springframework.cloud.bootstrap.BootstrapConfiguration注解的class,BootstrapApplicationListener主要目的是查找ApplicationContextInitializer並加入到SpringApplication的initializers列表中,用於監聽后續正式Context事件。
代碼調用路徑:org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent =》 org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext
這里通過SpringApplicationBuilder構建臨時Context,build的source為org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration


這里通過org.springframework.cloud.bootstrap.BootstrapImportSelector加載返回字符串對應的class實例到IOC容器。至於@Import注解的分析見另外一篇博文。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates
// 獲取通過SpringFactories方式以org.springframework.cloud.bootstrap.BootstrapConfiguration為key擴展的所有class類名 List<String> names = new ArrayList<>(SpringFactoriesLoader .loadFactoryNames(BootstrapConfiguration.class, classLoader));
// 這里有個擴展點,可以通過spring.cloud.bootstrap.sources加載Bootstrap相關bean names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray( this.environment.getProperty("spring.cloud.bootstrap.sources", "")))); List<OrderedAnnotatedElement> elements = new ArrayList<>(); for (String name : names) { try { elements.add( new OrderedAnnotatedElement(this.metadataReaderFactory, name)); } catch (IOException e) { continue; } } AnnotationAwareOrderComparator.sort(elements); String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new); return classNames; }
這里有個跟本篇主題相關的Spring Cloud擴展BootstrapConfiguration的兩個實現:

構建完context后回到onApplicationEvent方法,調用org.springframework.cloud.bootstrap.BootstrapApplicationListener#apply。

回到org.springframework.boot.SpringApplication#run(java.lang.String...)方法,

prepareContext
了解spring源碼的人應該知道,bean的創建等都是在Context的refresh中。這里再refresh之前做些環境數據的准備工作。
org.springframework.boot.SpringApplication#prepareContext =》org.springframework.boot.SpringApplication#applyInitializers
循環調用所有initializer的initialize方法:

看下上文提到的Spring Cloud擴展org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration#initialize,重點代碼片段如下,通過propertySourceLocators獲取PropertySource:

問題來了,propertySourceLocators是哪里來的?還記得PropertySourceBootstrapConfiguration是怎么構建的嗎?沒錯,就是以org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration為source,創建臨時Context加載生成的bean,這里會加載所有以org.springframework.cloud.bootstrap.BootstrapConfiguration為key的bean,我們的主角登場了,Spring Cloud Alibaba Nacos Config擴展了Spring Cloud的這個點:

com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration自動創建了NacosPropertySourceLocator:

最終會調用com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#locate:
@Override public PropertySource<?> locate(Environment env) { nacosConfigProperties.setEnvironment(env); ConfigService configService = nacosConfigManager.getConfigService(); if (null == configService) { log.warn("no instance of config service found, can't load config from nacos"); return null; } long timeout = nacosConfigProperties.getTimeout(); nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout); String name = nacosConfigProperties.getName(); // 優先使用配置的spring.cloud.nacos.config.prefix String dataIdPrefix = nacosConfigProperties.getPrefix();
// 如果沒有配置prefix,嘗試使用spring.cloud.nacos.config.name if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = name; } // 還是沒有的話,使用spring.application.name if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = env.getProperty("spring.application.name"); } CompositePropertySource composite = new CompositePropertySource( NACOS_PROPERTY_SOURCE_NAME); // 加載共享配置 loadSharedConfiguration(composite);
// 加載擴展配置 loadExtConfiguration(composite);
// 加載應用配置 loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env); return composite; }
com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadApplicationConfiguration =》 com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadNacosDataIfPresent =》com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadNacosPropertySource =》com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder#build =》com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder#loadNacosData =》com.alibaba.nacos.client.config.NacosConfigService#getConfig
最終通過nacos client的configService從服務端獲取數據並封裝成PropertySource返回。
