SpringBoot的啟動過程
今天我們通過調試的方式講述一下SpringBoot的啟動的過程,加深自己的了解。
一、運作環境介紹
我們的運作環境是web的運作環境。
JDK:1.8
SpringBoot:2.2.2.RELEASE
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.breakpointgroupId>
<artifactId>spring-boot-learnartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<executable>trueexecutable>
configuration>
plugin>
plugins>
build>
project>
二、啟動的過程
2.1 建立SpringApplication對象并且執行run方法。
//org.springframework.context.ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
根據上面的執行的方式,可以總結為Spring啟動的時候,一共做了2件事情:
1)、建立SpringApplication對象
2)、執行這個對象的run方法。
那麼接下來分别看一看這2個方法都給咱們做了哪些的操作?
2.2 建立SpringApplication對象
建立SpringApplication對象是SpringBoot執行的第一個步驟:
// org.springframework.boot.SpringApplication#SpringApplication()
//
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 設定資源的加載對象
this.resourceLoader = resourceLoader;
//判斷primarySources是不是null
Assert.notNull(primarySources, "PrimarySources must not be null");
// 設定主要資源的對象
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 決定目前應用的類别
// 由于我們用的是web,是以目前的應用視為 WebApplicationType.SERVLET 是一個服務
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 從項目的META-INF/spring.factories檔案裡找到所有的ApplicationContextInitializer的元件,設定到this.initializers對象上
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 這個和上面的相似,将META-INF/spring.factories所有的ApplicationListener對象擷取出來,設定到this.listeners這個對象上
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 決定哪一個類是祝啟動類
/*
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
上面的方式用了一個抛出異常的方式來決定哪一個類的裡面存在main方法。
*/
this.mainApplicationClass = deduceMainApplicationClass();
}
決定目前的應用是web還是
2.3 執行run方法
下面的代碼就是執行所有的run的方法。
public ConfigurableApplicationContext run(String... args) {
// 建立一個螢幕,監視整個項目的啟動,執行的開始時間以及結束的時間
StopWatch stopWatch = new StopWatch();
// 開始監視
stopWatch.start();
// 生命ConfigurableApplicationContext對象,也就是IOC容器,後面有具體的建立的過程
ConfigurableApplicationContext context = null;
// 建立 exceptionReporters 、也就是異常報告對象,如果我們的應用在啟動的過場中,出現了異常,這個時候,會報告相關的操作
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設定 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 的資訊
configureHeadlessProperty();
// 從 META-INF/spring.factories 擷取到所有的SpringApplicationRunListener元件
SpringApplicationRunListeners listeners = getRunListeners(args);
// 這個listeners開始監聽
listeners.starting();
try {
// 設定應用的啟動參數 也就是main防範的args參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 準備環境并且配置環境設定以及綁定到bindToSpringApplication上
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置環境
configureIgnoreBeanInfo(environment);
// 列印Banner 也就是springboot的圖示
Banner printedBanner = printBanner(environment);
// 建立IOC容器對象,建立的過程中,根據不同的類别建立不同的IOC容器對象
/*
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
*/
context = createApplicationContext();
// 擷取到錯誤的報告
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//準備上下文環境
// 1/IOC容器設定環境
// 2.執行ApplicationContext 的後置處理器
// 3.調用ApplicationContextInitializer 的initialize方法在refreshed之前
// 4. 家在bean的定義到IOC容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 重新整理IOC容器
// 實際上就是調用Spring啟動的核心的過程,調用各種的後置處理器以及初始化我們的bean的元件,并且将他們加入到IOC的容器中。
// 這一步,也建立了enbedded的tomcat的對象,并且在我們的配置好的端口上啟動了
refreshContext(context);
// 準備好IOC的容器後的操作
afterRefresh(context, applicationArguments);
// 停止應用的監控,實際上,我們的主要的應用已經啟動完成啦
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// SpringApplicationRunListener 的元件開始監聽,也就是調用arted(context);的方法
listeners.started(context);
// 調用所有的Runners
/*
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
根據上面的代碼,可以知道 調用了所有的ApplicationRunner 以及 CommandLineRunner的元件,也就是最後的一次回調了。
*/
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 如果有異常,那麼就會報告異常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 運作listeners.running
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
三、後記
經過上面的調試,更加的清晰的認識了SpringBoot的啟動過程,SpringBoot還有其他許多的東西,比如自動裝配等等這些比較重要的知識。