簡介
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) {
// 設置應用Context的父容器
applicationContext.setParent(this.parent);
}
}
總結
- SpringCloud Context模塊的功能:主要是構建了一個Bootstrap容器,並讓其成為原有的SpringBoot程序構建的容器的父容器。
- Bootstrap容器的作用:是為了預先完成一些Bean的實例化工作,可以把Bootstrap容器看作是先頭部隊。
- Bootstrap容器的構建:是利用了SpringBoot的事件機制,當SpringBoot的初始化Environment准備好之后會發布一個事件,這個事件的監聽器將負責完成Bootstrap容器的創建。構建時是使用SpringApplicationBuilder類來完成的。
- 如何讓Bootstrap容器與應用Context 建立父子關系:由於Bootstrap容器與應用Context都是關聯着同一個SpringApplication實例,Bootstrap容器自己完成初始化器的調用之后,會動態添加了一個初始化器 AncestorInitializer,相當於給應用Context埋了個雷,這個初始化器在應用容器進行初始化器調用執行時,完成父子關系的設置。