涨姿势:Spring Boot 2.x 启动全过程源码分析
Spring Boot 作为 Spring 生态中极为重要的框架,极大地简化了 Spring 应用的搭建和开发过程。它的自动配置功能让开发者无需繁琐的 XML 配置,就可以快速搭建出一个可用的应用。理解 Spring Boot 的启动过程,对于我们深入掌握这个框架,解决开发过程中遇到的问题,以及进行性能调优都具有重要意义。本文将深入分析 Spring Boot 2.x 的启动全过程源码,帮助大家理解其核心机制。
目录#
- Spring Boot 启动入口
- 创建 SpringApplication 实例
- 调用 run 方法启动应用
- 准备环境
- 创建和刷新应用上下文
- 应用上下文刷新完成操作
- 总结与参考
1. Spring Boot 启动入口#
在 Spring Boot 应用中,通常我们会有一个包含 main 方法的类,并且使用 @SpringBootApplication 注解标注。示例代码如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}SpringApplication.run(MyApplication.class, args) 是 Spring Boot 应用的启动入口。我们进入 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);
}可以看到,这里首先创建了一个 SpringApplication 实例,然后调用其 run 方法来启动应用。
2. 创建 SpringApplication 实例#
new SpringApplication(primarySources) 这行代码会创建一个 SpringApplication 对象。我们看一下 SpringApplication 类的构造函数:
public SpringApplication(Class<?>... primarySources) {
this(null, 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<? extends ApplicationContextInitializer<?>>) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection<? extends ApplicationListener<?>>) getSpringFactoriesInstances(
ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}在构造函数中,主要完成了以下几个操作:
- 初始化
primarySources,即我们传入的主配置类。 - 推断应用的类型(
WebApplicationType),根据类路径下的类来判断是 Servlet Web 应用、Reactive Web 应用还是非 Web 应用。 - 通过
getSpringFactoriesInstances方法加载ApplicationContextInitializer和ApplicationListener类型的实例,这些实例会在后续的启动过程中发挥作用。 - 推断主应用类。
3. 调用 run 方法启动应用#
创建好 SpringApplication 实例后,调用其 run 方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}run 方法是整个启动过程的核心,它完成了多个重要步骤,下面我们逐步分析。
4. 准备环境#
在 run 方法中,prepareEnvironment 方法用于准备应用的运行环境:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建并配置环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
bindToSpringApplication(environment);
if (!CollectionUtils.isEmpty(this.additionalProfiles)) {
environment.addActiveProfiles(this.additionalProfiles.toArray(new String[0]));
}
ConfigurationPropertySources.attach(environment);
return environment;
}这个方法主要完成以下操作:
- 创建并配置环境对象,根据应用类型创建不同的环境(如 Servlet 环境、Reactive 环境等)。
- 触发
SpringApplicationRunListeners的environmentPrepared事件,通知监听器环境已经准备好。 - 绑定环境到
SpringApplication实例。 - 添加额外的配置文件。
- 附加配置属性源到环境中。
5. 创建和刷新应用上下文#
创建应用上下文#
在 run 方法中,createApplicationContext 方法用于创建应用上下文:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
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);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable to create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}根据应用的类型(Servlet、Reactive 或非 Web),创建不同类型的应用上下文。
刷新应用上下文#
refreshContext 方法调用 AbstractApplicationContext 的 refresh 方法来刷新应用上下文:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
((ConfigurableApplicationContext) applicationContext).refresh();
}AbstractApplicationContext 的 refresh 方法是 Spring 框架的核心方法,它完成了 Bean 定义的加载、Bean 的实例化、依赖注入等一系列操作。
6. 应用上下文刷新完成操作#
在应用上下文刷新完成后,run 方法还会执行一些后续操作:
afterRefresh:用于在应用上下文刷新完成后执行一些自定义的操作。listeners.started(context):触发SpringApplicationRunListeners的started事件,通知监听器应用上下文已经启动。callRunners:调用ApplicationRunner和CommandLineRunner类型的 Bean,这些 Bean 可以在应用启动后执行一些初始化操作。
总结#
通过对 Spring Boot 2.x 启动全过程源码的分析,我们了解了 Spring Boot 应用从启动入口开始,到创建 SpringApplication 实例,再到准备环境、创建和刷新应用上下文,最后完成启动的整个过程。理解这个过程有助于我们更好地使用 Spring Boot 框架,解决开发过程中遇到的问题。
参考#
- Spring Boot 官方文档
- Spring Framework 官方文档
- 《Spring Boot实战》