前言
本文会对Springboot启动流程进行详细分析。但是请注意,Springboot启动流程是Springboot的逻辑,请千万不要将Springboot启动流程相关逻辑与Spring的相关逻辑混在一起,比如把Spring的bean生命周期的逻辑混在Springboot启动流程中,那么整个体系就复杂且混乱了。
所以本文仅重点关注Springboot启动流程,涉及Spring的部分,会略作说明并跳过。
整体的一个结构图如下。
Springboot版本:2.4.1
正文
一. Springboot启动流程图及说明
如下是Springboot的一个启动流程图。
在SpringApplication完成初始化后,就会调用SpringApplication对象的run() 方法,该方法就是Springboot启动的入口,也对应着全流程图中的开始。下面给出SpringApplication对象的run() 方法说明,如下所示。
public ConfigurableApplicationContext run(String... args) {
// 创建StopWatch,用于统计Springboot启动的耗时
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 获取运行时监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 调用运行时监听器的starting()方法
// 该方法需要在Springboot一启动时就调用,用于特别早期的初始化
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 获取args参数对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 读取Springboot配置文件并创建Environment对象
// 这里创建的Environment对象实际为ConfigurableEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印Banner图标
Banner printedBanner = printBanner(environment);
// 创建ApplicationContext应用行下文,即创建容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 准备容器
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 初始化容器
refreshContext(context);
afterRefresh(context, applicationArguments);
// 停止计时
stopWatch.stop();
if (this.logStartupInfo) {
// 打印启动耗时等信息
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 调用运行时监听器的started()方法
// 该方法需要在应用程序启动后,CommandLineRunners和ApplicationRunners被调用前执行
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 调用运行时监听器的running()方法
// 该方法需要在SpringApplication的run()方法执行完之前被调用
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
复制代码
二. SpringApplication的初始化
通常,Springboot应用程序的启动类定义如下。
@SpringBootApplication
public class LearnStartApplication {
public static void main(String[] args) {
SpringApplication.run(LearnStartApplication.class, args);
}
}
复制代码
从SpringApplication的静态run() 方法一路跟进,会发现如下的实现。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
复制代码
也就是Springboot启动时会先创建SpringApplication,然后再通过SpringApplication的run() 方法完成启动。所以下面分析一下SpringApplication的初始化逻辑,其构造方法如下所示。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 设置源
// 通常Springboot的启动类就是源
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断并设置WEB应用程序类型
// 根据classpath下的类来推断
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载并设置Bootstrapper
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
// 加载并设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载并设置应用事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断并设置应用程序主类的Class对象
this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码
梳理一下在SpringApplication的构造方法中,做了如下事情。
- 设置源。通常,Springboot中的源就是Springboot的启动类;
- 设置WEB应用程序类型。通过判断classpath下是否存在某些类,来推断当前WEB应用程序的类型;
- 加载并设置Bootstrapper,ApplicationContextInitializer和ApplicationListener。借助SpringFactoriesLoader基于SPI机制完成Bootstrapper,ApplicationContextInitializer和ApplicationListener的加载,然后设置到SpringApplication中;
- 设置应用程序主类的Class对象。
下面对上述事情进行分析。
1. 设置源
这里的源,也就是Spring容器启动时依赖的初始配置类,在Springboot中,初始配置类通常为启动类。下面可以通过调试看一下primarySources字段的值,如下所示。
可见源就是Springboot的启动类的Class对象。
2. 设置WEB应用程序类型
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 St