SpringBoot的啟動很簡單,代碼如下:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
從代碼上可以看出,調(diào)用了SpringApplication的靜態(tài)方法run。這個run方法會構(gòu)造一個SpringApplication的實例,然后再調(diào)用這里實例的run方法就表示啟動SpringBoot。
因此,想要分析SpringBoot的啟動過程,我們需要熟悉SpringApplication的構(gòu)造過程以及SpringApplication的run方法執(zhí)行過程即可。
我們以上述這段代碼為例,分析SpringBoot的啟動過程。
SpringApplication的構(gòu)造過程
SpringApplication構(gòu)造的時候內(nèi)部會調(diào)用一個private方法initialize:
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設(shè)置到SpringApplication的sources屬性中,目前只是一個MyApplication類對象
}
this.webEnvironment = deduceWebEnvironment(); // 判斷是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必須在類加載器中存在),并設(shè)置到webEnvironment屬性中
// 從spring.factories文件中找出key為ApplicationContextInitializer的類并實例化后設(shè)置到SpringApplication的initializers屬性中。這個過程也就是找出所有的應(yīng)用程序初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 從spring.factories文件中找出key為ApplicationListener的類并實例化后設(shè)置到SpringApplication的listeners屬性中。這個過程就是找出所有的應(yīng)用程序事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找出main類,這里是MyApplication類
this.mainApplicationClass = deduceMainApplicationClass();
}
ApplicationContextInitializer,應(yīng)用程序初始化器,做一些初始化的工作:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C applicationContext);
}
ApplicationListener,應(yīng)用程序事件(ApplicationEvent)監(jiān)聽器:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
這里的應(yīng)用程序事件(ApplicationEvent)有應(yīng)用程序啟動事件(ApplicationStartedEvent),失敗事件(ApplicationFailedEvent),準(zhǔn)備事件(ApplicationPreparedEvent)等。
應(yīng)用程序事件監(jiān)聽器跟監(jiān)聽事件是綁定的。比如ConfigServerBootstrapApplicationListener只跟ApplicationEnvironmentPreparedEvent事件綁定,LiquibaseServiceLocatorApplicationListener只跟ApplicationStartedEvent事件綁定,LoggingApplicationListener跟所有事件綁定等。
默認(rèn)情況下,initialize方法從spring.factories文件中找出的key為ApplicationContextInitializer的類有:
- org.springframework.boot.context.config.DelegatingApplicationContextInitializer
- org.springframework.boot.context.ContextIdApplicationContextInitializer
- org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
- org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
- org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
key為ApplicationListener的有:
- org.springframework.boot.context.config.ConfigFileApplicationListener
- org.springframework.boot.context.config.AnsiOutputApplicationListener
- org.springframework.boot.logging.LoggingApplicationListener
- org.springframework.boot.logging.ClasspathLoggingApplicationListener
- org.springframework.boot.autoconfigure.BackgroundPreinitializer
- org.springframework.boot.context.config.DelegatingApplicationListener
- org.springframework.boot.builder.ParentContextCloserApplicationListener
- org.springframework.boot.context.FileEncodingApplicationListener
- org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
SpringApplication的執(zhí)行
分析run方法之前,先看一下SpringApplication中的一些事件和監(jiān)聽器概念。
首先是SpringApplicationRunListeners類和SpringApplicationRunListener類的介紹。
SpringApplicationRunListeners內(nèi)部持有SpringApplicationRunListener集合和1個Log日志類。用于SpringApplicationRunListener監(jiān)聽器的批量執(zhí)行。
SpringApplicationRunListener看名字也知道用于監(jiān)聽SpringApplication的run方法的執(zhí)行。
它定義了5個步驟:
- started(run方法執(zhí)行的時候立馬執(zhí)行;對應(yīng)事件的類型是ApplicationStartedEvent)
- environmentPrepared(ApplicationContext創(chuàng)建之前并且環(huán)境信息準(zhǔn)備好的時候調(diào)用;對應(yīng)事件的類型是ApplicationEnvironmentPreparedEvent)
- contextPrepared(ApplicationContext創(chuàng)建好并且在source加載之前調(diào)用一次;沒有具體的對應(yīng)事件)
- contextLoaded(ApplicationContext創(chuàng)建并加載之后并在refresh之前調(diào)用;對應(yīng)事件的類型是ApplicationPreparedEvent)
- finished(run方法結(jié)束之前調(diào)用;對應(yīng)事件的類型是ApplicationReadyEvent或ApplicationFailedEvent)
SpringApplicationRunListener目前只有一個實現(xiàn)類EventPublishingRunListener,它把監(jiān)聽的過程封裝成了SpringApplicationEvent事件并讓內(nèi)部屬性(屬性名為multicaster)ApplicationEventMulticaster接口的實現(xiàn)類SimpleApplicationEventMulticaster廣播出去,廣播出去的事件對象會被SpringApplication中的listeners屬性進(jìn)行處理。
所以說SpringApplicationRunListener和ApplicationListener之間的關(guān)系是通過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯(lián)系起來的。
SpringApplication的run方法代碼如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); // 構(gòu)造一個任務(wù)執(zhí)行觀察器
stopWatch.start(); // 開始執(zhí)行,記錄開始時間
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 獲取SpringApplicationRunListeners,內(nèi)部只有一個EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 上面分析過,會封裝成SpringApplicationEvent事件然后廣播出去給SpringApplication中的listeners所監(jiān)聽
// 這里接受ApplicationStartedEvent事件的listener會執(zhí)行相應(yīng)的操作
listeners.started();
try {
// 構(gòu)造一個應(yīng)用程序參數(shù)持有類
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 創(chuàng)建Spring容器
context = createAndRefreshContext(listeners, applicationArguments);
// 容器創(chuàng)建完成之后執(zhí)行額外一些操作
afterRefresh(context, applicationArguments);
// 廣播出ApplicationReadyEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
listeners.finished(context, null);
stopWatch.stop(); // 執(zhí)行結(jié)束,記錄執(zhí)行時間
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context; // 返回Spring容器
}
catch (Throwable ex) {
handleRunFailure(context, listeners, ex); // 這個過程報錯的話會執(zhí)行一些異常操作、然后廣播出ApplicationFailedEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
throw new IllegalStateException(ex);
}
}
創(chuàng)建容器的方法createAndRefreshContext如下:
private ConfigurableApplicationContext createAndRefreshContext(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableApplicationContext context; // 定義Spring容器
// 創(chuàng)建應(yīng)用程序的環(huán)境信息。如果是web程序,創(chuàng)建StandardServletEnvironment;否則,創(chuàng)建StandardEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置一些環(huán)境信息。比如profile,命令行參數(shù)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 廣播出ApplicationEnvironmentPreparedEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
listeners.environmentPrepared(environment);
// 環(huán)境信息的校對
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(); // 創(chuàng)建Spring容器
context.setEnvironment(environment); // 設(shè)置Spring容器的環(huán)境信息
postProcessApplicationContext(context); // 回調(diào)方法,Spring容器創(chuàng)建之后做一些額外的事
applyInitializers(context); // SpringApplication的的初始化器開始工作
// 遍歷調(diào)用SpringApplicationRunListener的contextPrepared方法。目前只是將這個事件廣播器注冊到Spring容器中
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 把應(yīng)用程序參數(shù)持有類注冊到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事件給相應(yīng)的監(jiān)聽器執(zhí)行
listeners.contextLoaded(context);
// Spring容器的刷新
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
return context;
}
Spring容器的創(chuàng)建createApplicationContext方法如下:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 如果是web程序,那么構(gòu)造org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext容器
// 否則構(gòu)造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);
}
Spring容器創(chuàng)建之后有個回調(diào)方法postProcessApplicationContext:
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.webEnvironment) { // 如果是web程序
if (context instanceof ConfigurableWebApplicationContext) { // 并且也是Spring Web容器
ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
if (this.beanNameGenerator != null) { // 如果SpringApplication設(shè)置了是實例命名生成器,注冊到Spring容器中
configurableContext.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
}
}
if (this.resourceLoader != null) { // 如果SpringApplication設(shè)置了資源加載器,設(shè)置到Spring容器中
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context)
.setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context)
.setClassLoader(this.resourceLoader.getClassLoader());
}
}
}
初始化器做的工作,比如ContextIdApplicationContextInitializer會設(shè)置應(yīng)用程序的id;AutoConfigurationReportLoggingInitializer會給應(yīng)用程序添加一個條件注解解析器報告等:
protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍歷每個初始化器,對調(diào)用對應(yīng)的initialize方法
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
Spring容器的刷新refresh方法內(nèi)部會做很多很多的事情:比如BeanFactory的設(shè)置,BeanFactoryPostProcessor接口的執(zhí)行、BeanPostProcessor接口的執(zhí)行、自動化配置類的解析、條件注解的解析、國際化的初始化等等。這部分內(nèi)容會在之后的文章中進(jìn)行講解。
run方法中的Spring容器創(chuàng)建完成之后會調(diào)用afterRefresh方法,代碼如下:
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
afterRefresh(context, args.getSourceArgs()); // 目前是個空實現(xiàn)
callRunners(context, args); // 調(diào)用Spring容器中的ApplicationRunner和CommandLineRunner接口的實現(xiàn)類
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
// 找出Spring容器中ApplicationRunner接口的實現(xiàn)類
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 找出Spring容器中CommandLineRunner接口的實現(xiàn)類
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 對runners進(jìn)行排序
AnnotationAwareOrderComparator.sort(runners);
// 遍歷runners依次執(zhí)行
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) { // 如果是ApplicationRunner,進(jìn)行ApplicationRunner的run方法調(diào)用
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) { // 如果是CommandLineRunner,進(jìn)行CommandLineRunner的run方法調(diào)用
callRunner((CommandLineRunner) runner, args);
}
}
}
這樣run方法執(zhí)行完成之后。Spring容器也已經(jīng)初始化完成,各種監(jiān)聽器和初始化器也做了相應(yīng)的工作。
總結(jié)
SpringBoot啟動的時候,不論調(diào)用什么方法,都會構(gòu)造一個SpringApplication的實例,然后調(diào)用這個實例的run方法,這樣就表示啟動SpringBoot。
在run方法調(diào)用之前,也就是構(gòu)造SpringApplication的時候會進(jìn)行初始化的工作,初始化的時候會做以下幾件事:
- 把參數(shù)sources設(shè)置到SpringApplication屬性中,這個sources可以是任何類型的參數(shù)。本文的例子中這個sources就是MyApplication的class對象
- 判斷是否是web程序,并設(shè)置到webEnvironment這個boolean屬性中
- 找出所有的初始化器,默認(rèn)有5個,設(shè)置到initializers屬性中
- 找出所有的應(yīng)用程序監(jiān)聽器,默認(rèn)有9個,設(shè)置到listeners屬性中
- 找出運行的主類(main class)
SpringApplication構(gòu)造完成之后調(diào)用run方法,啟動SpringApplication,run方法執(zhí)行的時候會做以下幾件事:
- 構(gòu)造一個StopWatch,觀察SpringApplication的執(zhí)行
- 找出所有的SpringApplicationRunListener并封裝到SpringApplicationRunListeners中,用于監(jiān)聽run方法的執(zhí)行。監(jiān)聽的過程中會封裝成事件并廣播出去讓初始化過程中產(chǎn)生的應(yīng)用程序監(jiān)聽器進(jìn)行監(jiān)聽
- 構(gòu)造Spring容器(ApplicationContext),并返回
3.1 創(chuàng)建Spring容器的判斷是否是web環(huán)境,是的話構(gòu)造AnnotationConfigEmbeddedWebApplicationContext,否則構(gòu)造AnnotationConfigApplicationContext
3.2 初始化過程中產(chǎn)生的初始化器在這個時候開始工作
3.3 Spring容器的刷新(完成bean的解析、各種processor接口的執(zhí)行、條件注解的解析等等) - 從Spring容器中找出ApplicationRunner和CommandLineRunner接口的實現(xiàn)類并排序后依次執(zhí)行
例子
寫了一個例子用來驗證分析的啟動邏輯,包括自定義的初始化器、監(jiān)聽器、ApplicationRunner和CommandLineRunner。
地址在:https://github.com/fangjian0423/springboot-analysis/tree/master/springboot-startup