不要搞笑哈,用了5年的SpringBoot框架,竟然不瞭解它的啟動過程?



SpringBoot的啟動很簡單,代碼如下:

<code>@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}/<code>

從代碼上可以看出,調用了SpringApplication的靜態方法run。這個run方法會構造一個SpringApplication的實例,然後再調用這裡實例的run方法就表示啟動SpringBoot。

因此,想要分析SpringBoot的啟動過程,我們需要熟悉SpringApplication的構造過程以及SpringApplication的run方法執行過程即可。

我們以上述這段代碼為例,分析SpringBoot的啟動過程。

SpringApplication的構造過程

SpringApplication構造的時候內部會調用一個private方法initialize:

<code>public SpringApplication(Object... sources) {
initialize(sources); // sources目前是一個MyApplication的class對象
}

private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources)); // 把sources設置到SpringApplication的sources屬性中,目前只是一個MyApplication類對象
}
this.webEnvironment = deduceWebEnvironment(); // 判斷是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必須在類加載器中存在),並設置到webEnvironment屬性中
// 從spring.factories文件中找出key為ApplicationContextInitializer的類並實例化後設置到SpringApplication的initializers屬性中。這個過程也就是找出所有的應用程序初始化器

setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 從spring.factories文件中找出key為ApplicationListener的類並實例化後設置到SpringApplication的listeners屬性中。這個過程就是找出所有的應用程序事件監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找出main類,這裡是MyApplication類
this.mainApplicationClass = deduceMainApplicationClass();
}/<code>

ApplicationContextInitializer,應用程序初始化器,做一些初始化的工作:

<code>public interface ApplicationContextInitializer {
\tvoid initialize(C applicationContext);
}
/<code>

ApplicationListener,應用程序事件(ApplicationEvent)監聽器:

<code>public interface ApplicationListener extends EventListener {
\tvoid onApplicationEvent(E event);
}
/<code>

這裡的應用程序事件(ApplicationEvent)有應用程序啟動事件(ApplicationStartedEvent),失敗事件(ApplicationFailedEvent),準備事件(ApplicationPreparedEvent)等。

應用程序事件監聽器跟監聽事件是綁定的。比如ConfigServerBootstrapApplicationListener只跟ApplicationEnvironmentPreparedEvent事件綁定,LiquibaseServiceLocatorApplicationListener只跟ApplicationStartedEvent事件綁定,LoggingApplicationListener跟所有事件綁定等。

默認情況下,initialize方法從spring.factories文件中找出的key為ApplicationContextInitializer的類有:

  1. org.springframework.boot.context.config.DelegatingApplicationContextInitializer
  2. org.springframework.boot.context.ContextIdApplicationContextInitializer
  3. org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
  4. org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
  5. org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

key為ApplicationListener的有:

  1. org.springframework.boot.context.config.ConfigFileApplicationListener
  2. org.springframework.boot.context.config.AnsiOutputApplicationListener
  3. org.springframework.boot.logging.LoggingApplicationListener
  4. org.springframework.boot.logging.ClasspathLoggingApplicationListener
  5. org.springframework.boot.autoconfigure.BackgroundPreinitializer
  6. org.springframework.boot.context.config.DelegatingApplicationListener
  7. org.springframework.boot.builder.ParentContextCloserApplicationListener
  8. org.springframework.boot.context.FileEncodingApplicationListener
  9. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

SpringApplication的執行

分析run方法之前,先看一下SpringApplication中的一些事件和監聽器概念。

首先是SpringApplicationRunListeners類和SpringApplicationRunListener類的介紹。

SpringApplicationRunListeners內部持有SpringApplicationRunListener集合和1個Log日誌類。用於SpringApplicationRunListener監聽器的批量執行。

SpringApplicationRunListener看名字也知道用於監聽SpringApplication的run方法的執行。

它定義了5個步驟:

  1. started(run方法執行的時候立馬執行;對應事件的類型是ApplicationStartedEvent)
  2. environmentPrepared(ApplicationContext創建之前並且環境信息準備好的時候調用;對應事件的類型是ApplicationEnvironmentPreparedEvent)
  3. contextPrepared(ApplicationContext創建好並且在source加載之前調用一次;沒有具體的對應事件)
  4. contextLoaded(ApplicationContext創建並加載之後並在refresh之前調用;對應事件的類型是ApplicationPreparedEvent)
  5. finished(run方法結束之前調用;對應事件的類型是ApplicationReadyEvent或ApplicationFailedEvent)

SpringApplicationRunListener目前只有一個實現類EventPublishingRunListener,它把監聽的過程封裝成了SpringApplicationEvent事件並讓內部屬性(屬性名為multicaster)ApplicationEventMulticaster接口的實現類SimpleApplicationEventMulticaster廣播出去,廣播出去的事件對象會被SpringApplication中的listeners屬性進行處理。

所以說SpringApplicationRunListener和ApplicationListener之間的關係是通過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯繫起來的。

不要搞笑哈,用了5年的SpringBoot框架,竟然不瞭解它的啟動過程?


SpringApplication的run方法代碼如下:

<code>public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); // 構造一個任務執行觀察器
stopWatch.start(); // 開始執行,記錄開始時間
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 獲取SpringApplicationRunListeners,內部只有一個EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 上面分析過,會封裝成SpringApplicationEvent事件然後廣播出去給SpringApplication中的listeners所監聽
// 這裡接受ApplicationStartedEvent事件的listener會執行相應的操作
listeners.started();
try {
// 構造一個應用程序參數持有類
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 創建Spring容器
context = createAndRefreshContext(listeners, applicationArguments);
// 容器創建完成之後執行額外一些操作
afterRefresh(context, applicationArguments);
// 廣播出ApplicationReadyEvent事件給相應的監聽器執行
listeners.finished(context, null);
stopWatch.stop(); // 執行結束,記錄執行時間
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context; // 返回Spring容器
}

catch (Throwable ex) {
handleRunFailure(context, listeners, ex); // 這個過程報錯的話會執行一些異常操作、然後廣播出ApplicationFailedEvent事件給相應的監聽器執行
throw new IllegalStateException(ex);
}
}/<code>

創建容器的方法createAndRefreshContext如下:

<code>private ConfigurableApplicationContext createAndRefreshContext(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableApplicationContext context; // 定義Spring容器
// 創建應用程序的環境信息。如果是web程序,創建StandardServletEnvironment;否則,創建StandardEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置一些環境信息。比如profile,命令行參數
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 廣播出ApplicationEnvironmentPreparedEvent事件給相應的監聽器執行
listeners.environmentPrepared(environment);
// 環境信息的校對
if (isWebEnvironment(environment) && !this.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}

if (this.bannerMode != Banner.Mode.OFF) { // 是否在控制檯上打印自定義的banner
printBanner(environment);
}

// Create, load, refresh and run the ApplicationContext
context = createApplicationContext(); // 創建Spring容器
context.setEnvironment(environment); // 設置Spring容器的環境信息
postProcessApplicationContext(context); // 回調方法,Spring容器創建之後做一些額外的事
applyInitializers(context); // SpringApplication的的初始化器開始工作
// 遍歷調用SpringApplicationRunListener的contextPrepared方法。目前只是將這個事件廣播器註冊到Spring容器中

listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}

// 把應用程序參數持有類註冊到Spring容器中,並且是一個單例
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);

Set<object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
// 廣播出ApplicationPreparedEvent事件給相應的監聽器執行
listeners.contextLoaded(context);

// Spring容器的刷新
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
return context;
}/<object>/<code>

Spring容器的創建createApplicationContext方法如下:

<code>protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 如果是web程序,那麼構造org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext容器
// 否則構造org.springframework.context.annotation.AnnotationConfigApplicationContext容器
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "

+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}/<code>

Spring容器創建之後有個回調方法postProcessApplicationContext:

<code> protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
\tif (this.webEnvironment) { // 如果是web程序
\t\tif (context instanceof ConfigurableWebApplicationContext) { // 並且也是Spring Web容器
\t\t\tConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
\t\t\tif (this.beanNameGenerator != null) { // 如果SpringApplication設置了是實例命名生成器,註冊到Spring容器中
\t\t\t\tconfigurableContext.getBeanFactory().registerSingleton(
\t\t\t\t\t\tAnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
\t\t\t\t\t\tthis.beanNameGenerator);
\t\t\t}
\t\t}
\t}
\tif (this.resourceLoader != null) { // 如果SpringApplication設置了資源加載器,設置到Spring容器中
\t\tif (context instanceof GenericApplicationContext) {
\t\t\t((GenericApplicationContext) context)
\t\t\t\t\t.setResourceLoader(this.resourceLoader);
\t\t}
\t\tif (context instanceof DefaultResourceLoader) {
\t\t\t((DefaultResourceLoader) context)
\t\t\t\t\t.setClassLoader(this.resourceLoader.getClassLoader());
\t\t}
\t}
}/<code>

初始化器做的工作,比如ContextIdApplicationContextInitializer會設置應用程序的id;AutoConfigurationReportLoggingInitializer會給應用程序添加一個條件註解解析器報告等:

<code>protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍歷每個初始化器,對調用對應的initialize方法
for (ApplicationContextInitializer initializer : getInitializers()) {
Class> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);

Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}/<code>

Spring容器的刷新refresh方法內部會做很多很多的事情:比如BeanFactory的設置,BeanFactoryPostProcessor接口的執行、BeanPostProcessor接口的執行、自動化配置類的解析、條件註解的解析、國際化的初始化等等。這部分內容會在之後的文章中進行講解。

run方法中的Spring容器創建完成之後會調用afterRefresh方法,代碼如下:

<code> protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
afterRefresh(context, args.getSourceArgs()); // 目前是個空實現
callRunners(context, args); // 調用Spring容器中的ApplicationRunner和CommandLineRunner接口的實現類
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
\tList<object> runners = new ArrayList<object>();
// 找出Spring容器中ApplicationRunner接口的實現類
\trunners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 找出Spring容器中CommandLineRunner接口的實現類
\trunners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 對runners進行排序
\tAnnotationAwareOrderComparator.sort(runners);
// 遍歷runners依次執行
\tfor (Object runner : new LinkedHashSet<object>(runners)) {
\t\tif (runner instanceof ApplicationRunner) { // 如果是ApplicationRunner,進行ApplicationRunner的run方法調用
\t\t\tcallRunner((ApplicationRunner) runner, args);
\t\t}
\t\tif (runner instanceof CommandLineRunner) { // 如果是CommandLineRunner,進行CommandLineRunner的run方法調用
\t\t\tcallRunner((CommandLineRunner) runner, args);
\t\t}
\t}

}/<object>/<object>/<object>/<code>

這樣run方法執行完成之後。Spring容器也已經初始化完成,各種監聽器和初始化器也做了相應的工作。

總結

SpringBoot啟動的時候,不論調用什麼方法,都會構造一個SpringApplication的實例,然後調用這個實例的run方法,這樣就表示啟動SpringBoot。

在run方法調用之前,也就是構造SpringApplication的時候會進行初始化的工作,初始化的時候會做以下幾件事:

  1. 把參數sources設置到SpringApplication屬性中,這個sources可以是任何類型的參數。本文的例子中這個sources就是MyApplication的class對象
  2. 判斷是否是web程序,並設置到webEnvironment這個boolean屬性中
  3. 找出所有的初始化器,默認有5個,設置到initializers屬性中
  4. 找出所有的應用程序監聽器,默認有9個,設置到listeners屬性中
  5. 找出運行的主類(main class)

SpringApplication構造完成之後調用run方法,啟動SpringApplication,run方法執行的時候會做以下幾件事:

  1. 構造一個StopWatch,觀察SpringApplication的執行
  2. 找出所有的SpringApplicationRunListener並封裝到SpringApplicationRunListeners中,用於監聽run方法的執行。監聽的過程中會封裝成事件並廣播出去讓初始化過程中產生的應用程序監聽器進行監聽
  3. 構造Spring容器(ApplicationContext),並返回
    3.1 創建Spring容器的判斷是否是web環境,是的話構造AnnotationConfigEmbeddedWebApplicationContext,否則構造AnnotationConfigApplicationContext
    3.2 初始化過程中產生的初始化器在這個時候開始工作
    3.3 Spring容器的刷新(完成bean的解析、各種processor接口的執行、條件註解的解析等等)
  4. 從Spring容器中找出ApplicationRunner和CommandLineRunner接口的實現類並排序後依次執行

例子

寫了一個例子用來驗證分析的啟動邏輯,包括自定義的初始化器、監聽器、ApplicationRunner和CommandLineRunner。


分享到:


相關文章: