Springboot 啓動流程源碼主流程分析
一 、 前半部分new SpringApplication(primarySources)
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- WebApplicationType.deduceFromClasspath(); 是根據所有加載的類中,是否存在下面的幾個類,判斷是什麼類型的應用,設置到SpringApplication類的webApplicationType屬性上,後面創建容器的時候(二中的6createApplicationContext() ),根據它來判斷該創建創建那種類型的容器。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
- 設置初始化的類,從META-INF/spring.factories中獲取ApplicationContextInitializer指定的所有類,設置到SpringApplication的initializers屬性上。
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
這個裏面getSpringFactoriesInstances 會獲取META-INF/spring.factories 文件中指定的類,是獲取所有當前應用中,jar包目錄中有META-INF/spring.factories文件,而不是某一個jar包下的。下圖是我調試時候的截圖。後續還會調用很多次這種getSpringFactoriesInstances方法,都從MultiValueMap類型的緩存中取,只在第一次的時候從文件中讀取,之後就緩存了。
3. 和2類似, 這次取得是ApplicationListener指定的類。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
- 根拒線程堆棧,找到main方法所在的類,主要用來打日誌。
this.mainApplicationClass = deduceMainApplicationClass();
二 、接下來看看後半部分的run方法。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
- 計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
stopWatch.stop();
stopWatch是一個計時器,可以統計start到stop之間所用的時間。
- 設置系統屬性java.awt.headless
this.configureHeadlessProperty();
這一句是表示運行在服務器端,在沒有顯示器和鼠標鍵盤的模式下工作,模擬輸入輸出設備功能
- 加載容器啓動監聽器
SpringApplicationRunListeners listeners=this.getRunListeners(args);
listeners.starting();
這兩句的作用是:
- 從META-INF/spring.factories 配置中獲取org.springframework.boot.SpringApplicationRunListener指定的類,只有一個org.springframework.boot.context.event.EventPublishingRunListener。加載並實例化
- 加載並實例化以後,調用他們的starting()方法。這個方法裏面發佈了一個ApplicationStartingEvent()事件。這個事件有四個支持的監聽者,如下圖
依次調用每個監聽者的onApplicationEvent()方法,例如LoggingApplicationListener的onApplicationEvent() 方法就是判斷一下是那種日誌系統,log4j、logback等等,然後執行一些日誌系統初始化的工作。
- 獲取配置
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
這幾句的作用是獲取系統的環境變量、系統屬性、還有我們自己的application.properties文件中的配置,下圖是調試時候的截圖,可以看到application.properties中的配置就是在prepareEnvironment(listeners, applicationArguments);這一步讀取進來的。
同時發佈ApplicationEnvironmentPreparedEvent事件,調用每個監聽者的監聽方法。
- 這個打印logo
Banner printedBanner = printBanner(environment);
- 獲取ConfigurableApplicationContext,就是根據1中設置的容器類型,創建容器類。
context = createApplicationContext();
- 加載org.springframework.boot.diagnostics.FailureAnalyzers 指定的類。這些類主要是在項目啓動失敗之後,處理異常,打印日誌等,他是在啓動異常的catch塊中調用的:
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
- 準備容器。 這一步主要是在容器刷新之前的準備動作:把之前解析好的環境environment、容器監聽器listeners、啓動時的參數applicationArguments等屬性設置到容器對象。其中包含一個非常關鍵的操作:將啓動類注入容器,爲後續開啓自動化配置奠定基礎。 容器能加載到啓動類,就能解析到啓動類上的註解,就能逐個的加載要掃描的類,包括各種配置類及他們的初始化工作。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//設置容器環境,包括各種變量
context.setEnvironment(environment);
//執行容器後置處理
postProcessApplicationContext(context);
//執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
applyInitializers(context);
//發送容器已經準備好的事件,通知各監聽器
listeners.contextPrepared(context);
//打印log
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//註冊啓動參數bean,這裏將容器指定的參數封裝成bean,注入容器
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
//設置banner
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
//獲取我們的啓動類指定的參數,可以是多個
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加載我們的啓動類,將啓動類注入容器
load(context, sources.toArray(new Object[0]));
//發佈容器已加載事件。
listeners.contextLoaded(context);
}
- 刷新容器。 spring的 ioc 和 aop的功能就在這裏實現。比較複雜。後面再來。
refreshContext(context);
- 刷新容器後的擴展接口, 設計模式中的模板方法,默認爲空實現。如果有自定義需求,可以重寫該方法。比如打印一些啓動結束log,或者一些其它後置處理。
afterRefresh(context, applicationArguments);
- 發佈容器啓動結束事件,有兩個監聽者,但是什麼事情都沒有做。
listeners.started(context);
- 執行所有實現了ApplicationRunner、CommandLineRunner接口的類中的run方法,經常用來自定義在容器啓動啓動完成後立即要做的事情。
callRunners(context, applicationArguments);
- 繼續發佈org.springframework.boot.context.event.ApplicationReadyEvent事件,調用該事件的監聽器的監聽方法。
try {
listeners.running(context);
}
有兩個作者我覺得寫得比較好的,我也參考了許多:
https://blog.csdn.net/qq_26000415/column/info/18708 寫得很詳細
https://blog.csdn.net/woshilijiuyi/article/details/82219585 寫得很精髓