基於前文對springcloud的引導,本文則從源碼角度查閱下cloud的context板塊的運行邏輯
前言
springcloud是基於springboot開發的,所以讀者在閱讀此文前最好已經了解了springboot的工作原理。本文將不闡述springboot的工作邏輯
Cloud Context
springboot cloud context在官方的文檔中在第一點被提及,是用戶ApplicationContext的父級上下文,筆者稱呼為BootstrapContext。根據springboot的加載機制,很多第三方以及重要的Configuration配置均是保存在了spring.factories文件中。
筆者翻閱了spring-cloud-context模塊下的對應文件,見如下
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
涉及的主要分三類,筆者優先分析監聽器,其一般擁有更高的優先級並跟其他兩塊有一定的關聯性。
除了日志監聽器筆者不太關注,其余兩個分步驟來分析
RestartListener
重啟監聽器,應該是用於刷新上下文的,直接查看下其復寫的方法
@Override
public void onApplicationEvent(ApplicationEvent input) {
// 應用預備事件,先緩存context
if (input instanceof ApplicationPreparedEvent) {
this.event = (ApplicationPreparedEvent) input;
if (this.context == null) {
this.context = this.event.getApplicationContext();
}
}
// 上下文刷新結束事件,重新傳播ApplicationPreparedEvent事件
else if (input instanceof ContextRefreshedEvent) {
if (this.context != null && input.getSource().equals(this.context)
&& this.event != null) {
this.context.publishEvent(this.event);
}
}
else {
// 上下文關閉事件傳播至此,則開始清空所擁有的對象
if (this.context != null && input.getSource().equals(this.context)) {
this.context = null;
this.event = null;
}
}
}
上述的刷新事件經過查閱,與org.springframework.cloud.context.restart.RestartEndpoint類有關,這個就后文再分析好了
BootstrapApplicationListener
按照順序分析此監聽器
1.優先看下其類結構
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
}
此監視器是用於響應ApplicationEnvironmentPreparedEvent應用環境變量預初始化事件,表明BootstrapContext的加載時機在用戶上下文之前,且其加載順序比ConfigFileApplicationListener監聽器超前,這點稍微強調下。
2.接下來分析下其復寫的方法onApplicationEvent(ApplicationEnvironmentPreparedEvent event)
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// 獲取環境變量對象
ConfigurableEnvironment environment = event.getEnvironment();
// 讀取spring.cloud.bootstrap.enabled環境屬性,默認為true。可通過系統變量設置
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
// 尋找當前環境是否已存在BootstrapContext
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
// 如果還沒有被創建,則開始創建
if (context == null) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
// 注冊注銷監聽器
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
// 加載BoostrapContext上的ApplicationContextInitializers到用戶Context上
apply(context, event.getSpringApplication(), environment);
}
邏輯很簡單,筆者梳理下
- spring.cloud.bootstrap.enabled 用於配置是否啟用BootstrapContext,默認為true。可采取系統變量設定
- spring.cloud.bootstrap.name 用於加載bootstrap對應配置文件的別名,默認為bootstrap
- BootstrapContext上的beanType為ApplicationContextInitializer類型的bean對象集合會被注冊至用戶的Context上
3.重點看下BootstrapContext的創建過程,源碼比較長,但筆者認為還是很有必要拿出來
/**
*
* create bootstrap context
*
* @param environment 全局Environment
* @param application 用戶對應的Application
* @param configName bootstrapContext對應配置文件的加載名,默認為bootstrap
* @return bootstrapContext
*/
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
// create empty environment
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
// 讀取spring.cloud.bootstrap.location屬性,一般通過系統變量設置,默認為空
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
// 加載bootstrapContext配置文件的路徑,與spring.config.name搭配使用
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// use SpringApplicationBuilder to create bootstrapContext
SpringApplicationBuilder builder = new SpringApplicationBuilder()
// 此處activeProfiles是通過系統變量設置的,此處稍微備注下
.profiles(environment.getActiveProfiles())
.bannerMode(Mode.OFF)
// 應用bootstrap本身的環境變量
.environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
// 配置入口函數類
if (builderApplication.getMainApplicationClass() == null) {
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
// 增加入口類BootstrapImportSelectorConfiguration
builder.sources(BootstrapImportSelectorConfiguration.class);
// create
final ConfigurableApplicationContext context = builder.run();
// 設置bootstrapContext的別名為bootstrap
context.setId("bootstrap");
// 配置bootstrapContext為用戶Context的父類
addAncestorInitializer(application, context);
// 合並defaultProperties對應的變量至childEnvironment
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
此處也對上述的代碼作下簡單的小結
- spring.cloud.bootstrap.location變量用於配置bootstrapContext配置文件的加載路徑,可用System設置,默認則采取默認的文件搜尋路徑;與spring.cloud.bootstrap.name搭配使用
- bootstrapContext對應的activeProfiles可采用spring.active.profiles系統變量設置,注意是System變量。當然也可以通過bootstrap.properties/bootstrap.yml配置文件設置
- bootstrapContext的重要入口類為BootstrapImportSelectorConfiguration,此也是下文的分析重點
- bootstrapContext的contextId為bootstrap。即使配置了spring.application.name屬性也會被設置為前者,且其會被設置為用戶Context的父類
- bootstrap.(yml|properties)上的配置會被合並至用戶級別的Environment中的defaultProperties集合中,且其相同的KEY會被丟棄,不同KEY會被保留。即其有最低的屬性優先級
通過上述的代碼均可以得知,bootstrapContext也是通過springboot常見的SpringApplication方式來創建的,但其肯定有特別的地方。
特別之處就在BootstrapImportSelectorConfiguration類,其也與上述spring.factories文件中org.springframework.cloud.bootstrap.BootstrapConfiguration的Key有直接的關系,我們下文重點分析
后記
由於繼續分析會導致篇幅過長,遂片段式,這樣有助於深入理解以及后期回顧。下文便會主要分析下bootstrapContext額外的特點。