涨姿势:Spring Boot 2.x 启动全过程源码分析

Spring Boot 作为 Spring 生态中极为重要的框架,极大地简化了 Spring 应用的搭建和开发过程。它的自动配置功能让开发者无需繁琐的 XML 配置,就可以快速搭建出一个可用的应用。理解 Spring Boot 的启动过程,对于我们深入掌握这个框架,解决开发过程中遇到的问题,以及进行性能调优都具有重要意义。本文将深入分析 Spring Boot 2.x 的启动全过程源码,帮助大家理解其核心机制。

目录#

  1. Spring Boot 启动入口
  2. 创建 SpringApplication 实例
  3. 调用 run 方法启动应用
  4. 准备环境
  5. 创建和刷新应用上下文
  6. 应用上下文刷新完成操作
  7. 总结与参考

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 方法加载 ApplicationContextInitializerApplicationListener 类型的实例,这些实例会在后续的启动过程中发挥作用。
  • 推断主应用类。

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 环境等)。
  • 触发 SpringApplicationRunListenersenvironmentPrepared 事件,通知监听器环境已经准备好。
  • 绑定环境到 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 方法调用 AbstractApplicationContextrefresh 方法来刷新应用上下文:

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();
}

AbstractApplicationContextrefresh 方法是 Spring 框架的核心方法,它完成了 Bean 定义的加载、Bean 的实例化、依赖注入等一系列操作。

6. 应用上下文刷新完成操作#

在应用上下文刷新完成后,run 方法还会执行一些后续操作:

  • afterRefresh:用于在应用上下文刷新完成后执行一些自定义的操作。
  • listeners.started(context):触发 SpringApplicationRunListenersstarted 事件,通知监听器应用上下文已经启动。
  • callRunners:调用 ApplicationRunnerCommandLineRunner 类型的 Bean,这些 Bean 可以在应用启动后执行一些初始化操作。

总结#

通过对 Spring Boot 2.x 启动全过程源码的分析,我们了解了 Spring Boot 应用从启动入口开始,到创建 SpringApplication 实例,再到准备环境、创建和刷新应用上下文,最后完成启动的整个过程。理解这个过程有助于我们更好地使用 Spring Boot 框架,解决开发过程中遇到的问题。

参考#