基於前文對springcloud的引導,本文則從源碼角度查閱下cloud的context板塊的運行邏輯html
springcloud是基於springboot開發的,因此讀者在閱讀此文前最好已經瞭解了springboot的工做原理。本文將不闡述springboot的工做邏輯java
springboot cloud context在官方的文檔中在第一點被說起,是用戶ApplicationContext的父級上下文,筆者稱呼爲BootstrapContext。根據springboot的加載機制,不少第三方以及重要的Configuration配置均是保存在了spring.factories文件中。
筆者翻閱了spring-cloud-context模塊下的對應文件,見以下web
# 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
涉及的主要分三類,筆者優先分析監聽器,其通常擁有更高的優先級並跟其餘兩塊有必定的關聯性。
除了日誌監聽器筆者不太關注,其他兩個分步驟來分析spring
重啓監聽器,應該是用於刷新上下文的,直接查看下其複寫的方法bootstrap
@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類有關,這個就後文再分析好了緩存
按照順序分析此監聽器springboot
1.優先看下其類結構app
public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered { }
此監視器是用於響應ApplicationEnvironmentPreparedEvent應用環境變量預初始化事件,代表BootstrapContext的加載時機在用戶上下文以前,且其加載順序比ConfigFileApplicationListener監聽器超前,這點稍微強調下。ide
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); }
邏輯很簡單,筆者梳理下
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; }
此處也對上述的代碼做下簡單的小結
經過上述的代碼都可以得知,bootstrapContext也是經過springboot常見的SpringApplication方式來建立的,但其確定有特別的地方。
特別之處就在BootstrapImportSelectorConfiguration類,其也與上述spring.factories文件中org.springframework.cloud.bootstrap.BootstrapConfiguration的Key有直接的關係,咱們下文重點分析
因爲繼續分析會致使篇幅過長,遂片斷式,這樣有助於深刻理解以及後期回顧。下文便會主要分析下bootstrapContext額外的特色。