SpringCloud這個框架本身是建立在SpringBoot基礎之上的,所以使用SpringCloud的方式與SpringBoot相仿。也是通過類似如下代碼進行啟動。
SpringApplication.run(XxxApplication.class, args);
其中 XxxApplication.class 類上也需要添加 @SpringBootApplication注解。
要使用SpringCloud框架,在pom文件中要確保引入 spring-cloud-starter 依賴包, spring-cloud-starter 依賴如下的 jar :
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> </dependencies>
其中 spring-cloud-context-x.y.z.RELEASE.jar 和 spring-cloud-commons-x.y.z.RELEASE.jar 下的 META-INF 目錄下都包含 spring.factories 文件,所以可以把這兩個jar看作是springCloud程序的入口。
SpringCloud在構建上下文 (即ApplicationContext實例)時,采用了Spring父子容器的設計,會在 SpringBoot構建的容器(后面稱之為應用容器)之上創建一父容器 Bootstrap Application Context .
那么SpringCloud設計出Bootstrap Application Context ,並把它作為 應用容器的父容器的目的是什么呢:
因為SpringCloud 作為一個微服務框架,需要使用全局的配置中心,而配置中心的配置是可以提供給應用容器的,所以在應用容器初始化和實例化Bean之前需要先完成配置中心的實例化,這個任務就由Bootstrap Application Context 來完成,而配置中心的相關配置屬性就從bootstrap.properties或bootstrap.yml文件中讀取。
但要注意的是,在Bootstrap Application Context 啟動工作完成之后,其從bootstrap.properties或bootstrap.yml文件中讀取的配置,是會被應用容器對應的application.properties或yml文件中的同名屬性覆蓋的。
下面從源碼角度來分析上面的論述:
1. 代碼運行時還是從SpringApplication實例的run方法開始 ,此處會觸發 BootstrapApplicationListener 類中的代碼 , Bootstrap Application Context 的創建就是通過這個監聽器觸發的
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty();
//此處會加載spring-cloud-context提供的監聽器org.springframework.cloud.bootstrap.BootstrapApplicationListener.class SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args);
//此處會發布ApplicationEnvironmentPreparedEvent事件,觸發BootstrapApplicationListener中的代碼 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
2. Bootstrap Application Context 的實例化 ,由BootstrapApplicationListener類的 onApplicationEvent方法觸發
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment();
//可以通過環境變量 spring.cloud.bootstrap.enabled來禁止使用Bootstrap容器 if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) { return; } // 由於Bootstrap容器在創建時還是會再次調用上面步驟1的代碼,還會再次觸發BootstrapApplicationListener類這個方法,所以此處作個判斷,
如果當前是Bootstrap容器的處理,則直接返回 if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { return; } ConfigurableApplicationContext context = null;
//獲取配置文件的名字,默認為bootstrap.properties或.yml ,並且這個名字可以通過 spring.cloud.bootstrap.name在環境中配置 String configName = environment .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}"); for (ApplicationContextInitializer<?> initializer : event.getSpringApplication() .getInitializers()) {
//從相應jar中的spring.factories文件中讀取初始化器的配置類實例,如果這個實例類是ParentContextApplicationContextInitializer類型
則直接從該類中獲取到父容器,默認情況下,沒有提供這樣的類,下面這段代碼會跳過 if (initializer instanceof ParentContextApplicationContextInitializer) { context = findBootstrapContext( (ParentContextApplicationContextInitializer) initializer, configName); } } if (context == null) {
//此處分析見步驟3 context = bootstrapServiceContext(environment, event.getSpringApplication(), configName); event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context)); } apply(context, event.getSpringApplication(), environment); }
3. bootstrap容器的創建
private ConfigurableApplicationContext bootstrapServiceContext( ConfigurableEnvironment environment, final SpringApplication application, String configName) { /**
此處代碼主要是從各處獲取屬性配置,此處忽略
**/
// TODO: is it possible or sensible to share a ResourceLoader? SpringApplicationBuilder builder = new SpringApplicationBuilder() .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF) .environment(bootstrapEnvironment) // Don't use the default properties in this builder .registerShutdownHook(false).logStartupInfo(false) .web(WebApplicationType.NONE);
//SpringApplicationBuilder的作用:1.構建SpringApplication 2.構建ApplicationContext
//這里需要思考一下,springboot在啟動時已經構建了一個SpringApplication實例,為何此處又構建了一個
//這是因為這個SpringApplication實例的構建環境和SringBoot原生構建的那個不同,看一下上一行代碼就能明白 final SpringApplication builderApplication = builder.application(); if(builderApplication.getMainApplicationClass() == null){ builder.main(application.getMainApplicationClass()); } if (environment.getPropertySources().contains("refreshArgs")) { builderApplication .setListeners(filterListeners(builderApplication.getListeners())); }
//從springFactories文件中查找BootstrapConfiguration的配置類 builder.sources(BootstrapImportSelectorConfiguration.class);
//構建出BootstrapContext final ConfigurableApplicationContext context = builder.run(); context.setId("bootstrap");
// 設置BootstrapContext成為應用Context的父容器,此處分析見步驟4 addAncestorInitializer(application, context); // It only has properties in it now that we don't want in the parent so remove // it (and it will be added back later) bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME); mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties); return context; }
4. 設置BootstrapContext成為應用Context的父容器 (在SpringApplication實例中動態添加了一個初始化器,相當於給應用Context埋了個雷)
private void addAncestorInitializer(SpringApplication application, ConfigurableApplicationContext context) { boolean installed = false;
//從spring.factories文件中獲取初始化器的配置類且類型為AncestorInitializer for (ApplicationContextInitializer<?> initializer : application .getInitializers()) { if (initializer instanceof AncestorInitializer) { installed = true; // New parent ((AncestorInitializer) initializer).setParent(context); } }
//默認情況下是沒有配圍置AncestorInitializer這樣的類,此處是則執行由BootstrapListener提供的內部類 if (!installed) {
//將BootstrapContext作為父容器傳到AncestorInitializer實例中,並將其放入SpringApplication實例的初始器列表中 application.addInitializers(new AncestorInitializer(context)); } }
5. 初始化器AncestorInitializer被觸發,是由應用Context的處理觸發的
public void initialize(ConfigurableApplicationContext context) { //這個context是應用Context while (context.getParent() != null && context.getParent() != context) { context = (ConfigurableApplicationContext) context.getParent(); } reorderSources(context.getEnvironment());
//完成應用容器的父容器的設置 new ParentContextApplicationContextInitializer(this.parent) .initialize(context); }
6. ParentContextApplicationContextInitializer代碼
private static class ParentContextApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { private final ApplicationContext parent; ParentContextApplicationContextInitializer(ApplicationContext parent) { this.parent = parent; } @Override public void initialize(ConfigurableApplicationContext applicationContext) { applicationContext.setParent(this.parent); //設置應用Context的父容器 } }
總結:
1. SpringCloud Context模塊的功能 :主要是構建了一個Bootstrap容器,並讓其成為原有的springboot程序構建的容器的父容器。
2. Bootstrap容器的作用:是為了預先完成一些bean的實例化工作,可以把Bootstrap容器看作是先頭部隊。
3. Bootstrap容器的構建:是利用了Springboot的事件機制,當 springboot 的初始化 Environment 准備好之后會發布一個事件,這個事件的監聽器將負責完成Bootstrap容器的創建。構建時是使用 SpringApplicationBuilder 類來完成的。
4. 如何讓Bootstrap容器與應用Context 建立父子關系 :由於Bootstrap容器與應用Context都是關聯着同一個SpringApplication實例,Bootstrap容器自己完成初始化器的調用之后,會動態添加了一個初始化器 AncestorInitializer,相當於給應用Context 埋了個雷 ,這個初始化器在應用容器進行初始化器調用執行時,完成父子關系的設置。