Spring Boot 启动流程分析

Spring Boot 简化了基于 Spring 的应用程序的创建和部署过程,其启动流程涉及多个步骤和组件。为了详细理解 Spring Boot 的启动流程,我们可以从源码的角度进行分析。以下是 Spring Boot 启动过程的详细介绍,结合关键源码类和方法进行说明。

应用程序的入口点

通常,Spring Boot 应用程序的启动入口是一个带有 main 方法的类,该类使用 @SpringBootApplication 注解。例如:

1
2
3
4
5
6
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

关键源码

  • SpringApplication.run 方法

    SpringApplication.run 是启动 Spring Boot 应用程序的主要方法。其源码位于 org.springframework.boot.SpringApplication 类中。

创建 SpringApplication 实例

当调用 SpringApplication.run 时,内部实际上是创建了一个 SpringApplication 的实例,并调用其 run 方法。

1
2
3
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

关键源码

  • SpringApplication 构造函数

    负责初始化应用程序的基本设置,包括检测是否为 Web 应用、设置默认属性等。豆腐浆肯德基疯狂

    豆腐

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 用于加载资源的接口,可以是类路径资源或文件系统资源
    this.resourceLoader = resourceLoader;
    // 主要的源类,通常是包含 @SpringBootApplication 注解的类。
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推断 Web 应用类型:根据类路径中的依赖项推断应用程序的类型(例如,是否是 Web 应用程序)。
    // 这会检查是否存在特定的类(如 Servlet、Reactive 等)。
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置应用初始化器:从 Spring 工厂中加载所有 ApplicationContextInitializer 的实例,并将其设置到应用程序中。
    // 初始化器用于在应用上下文刷新之前进行配置。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置应用监听器:同样,从 Spring 工厂中加载所有 ApplicationListener 的实例,并将其设置到应用程序中。
    // 监听器用于响应应用程序事件。
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推断主应用类:通过分析 primarySources 确定主应用类(通常是包含 main 方法的类),这对配置和后续操作非常重要。
    this.mainApplicationClass = deduceMainApplicationClass();
    }

    其中,getSpringFactoriesInstances 方法会调用静态方法 SpringFactoriesLoader.loadFactoryNames ,该方法读取 META-INF/spring.factories, 获取各种自动配置类,如 Initializers、Application Listeners、Import Listeners、Import Filters、Auto Configure、Failure analyzers、Template availability providers。在这里,主要提前获取 ApplicationContextInitializer 和 ApplicationListener 的实例。

准备 SpringApplication 实例

run 方法中,SpringApplication 实例会经过一系列的准备工作,例如绑定命令行参数、设置环境等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public ConfigurableApplicationContext run(String... args) {
// 使用 StopWatch 记录启动时间,以便后续性能分析。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置无头属性:设置 Java 的无头模式属性,这对于某些环境(如服务器)很重要
configureHeadlessProperty();
// 从 sping.factories 获取 SpringApplicationRunListener 实例,通知所有监听器应用程序启动的开始。
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境:调用 prepareEnvironment 方法,准备应用环境配置,包括属性源、活动配置文件等。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建应用上下文:根据webApplicationType,创建 AnnotationConfigApplicationContext
// AnnotationConfigServletWebServerApplicationContext 或
// AnnotationConfigReactiveWebServerApplicationContext
context = createApplicationContext();
// 获取异常报告器:从 Spring 工厂加载所有 SpringBootExceptionReporter 实例,这些用于处理启动过程中的异常。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文:配置上下文,包括设置环境、postProcessApplicationContext,
// 调用 ApplicationContextInitializer
// 配置BeanFactory
// 根据 lazyInitialization 与否添加 LazyInitializationBeanFactoryPostProcessor
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 核心,这里最终调用 AbstractApplicationContext 的refresh,刷新上下文
// 初始化 Spring 上下文,创建和注册 Bean,进行依赖注入等。
refreshContext(context);
// 扩展方法,用于刷新上下文后的处理,由子类实现
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 调用所有 ApplicationRunner 或 CommandLineRunner
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;
}

关键源码

  • prepareEnvironment 方法

    负责创建和配置 Environment 对象,包括加载默认的配置属性源。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private ConfigurableEnvironment prepareEnvironment() {
    // 创建StandardEnvironment或者StandardServletEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 增加属性源,例如命令行参数、系统属性等
    configureEnvironment(environment);
    // 设置启动参数
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    deduceEnvironmentClass());
    }
    // 再设置一次
    ConfigurationPropertySources.attach(environment);
    return environment;
    }
  • createApplicationContext 方法

    根据应用类型(如 Web 应用或非 Web 应用),SpringApplication 会创建不同类型的 ApplicationContext。对于 Web 应用,通常是 AnnotationConfigServletWebServerApplicationContext

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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 create a default ApplicationContext, please specify an ApplicationContextClass", ex);
    }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
  • prepareContext 方法

    将环境配置、命令行参数、启动横幅等信息整合到应用上下文中,并通过监听器通知各个阶段的准备情况。这一过程确保了上下文的正确配置,并为后续的上下文刷新和 Bean 初始化做好了准备。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
    SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    // 后处理上下文:调用 postProcessApplicationContext 方法,
    // 可以进行自定义的处理,比如添加额外的配置或 Bean 定义。
    postProcessApplicationContext(context);
    // 应用初始化器:调用所有注册的 ApplicationContextInitializer,这些初始化器可以在上下文刷新之前对其进行配置。
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
    }
    // 添加 Boot 特殊的单例 beans,
    // 获取应用上下文的 BeanFactory,这个工厂负责管理 Bean 的生命周期和依赖关系。
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    // 注册应用参数:将 applicationArguments 作为单例 Bean 注册到 BeanFactory,
    // 以便在应用程序中可以通过依赖注入访问。
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    // 注册横幅:如果存在打印的横幅,将其作为单例 Bean 注册到 BeanFactory,以便在应用程序中使用。
    if (printedBanner != null) {
    beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    // 允许 Bean 定义覆盖:如果 BeanFactory 是 DefaultListableBeanFactory 的实例,
    // 设置是否允许 Bean 定义的覆盖。这允许用户在特定情况下定义相同名称的 Bean。
    if (beanFactory instanceof DefaultListableBeanFactory) {
    ((DefaultListableBeanFactory) beanFactory)
    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // 添加懒加载处理器:如果设置了懒加载,则向上下文添加 BeanFactoryPostProcessor,
    // 以便在 Bean 被请求时才初始化它们。
    if (this.lazyInitialization) {
    context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // 获取所有源:调用 getAllSources 方法获取所有源(例如配置类、配置文件等),并确保这些源不为空。
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 加载源:将所有源加载到上下文中。这一步主要负责注册 Bean 定义、执行配置等。
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
    }
  • applyInitializers 方法

    在prepareContext方法中,调用 applyInitializers 方法,应用所有注册的 ApplicationContextInitializer,允许在上下文刷新之前自定义上下文。

    1
    2
    3
    4
    5
    private void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : this.initializers) {
    initializer.initialize(context);
    }
    }
  • load 方法

    在prepareContext方法中,调用 load 方法,将源中的 Bean 定义加载到应用上下文中,并配置相关的加载器参数。这一过程确保了 Spring 应用程序能够根据给定的配置正确地初始化和管理所有的 Bean。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
    logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
    loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
    loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
    loader.setEnvironment(this.environment);
    }
    loader.load();
    }

刷新上下文

SpringApplication 利用 SpringFactoriesLoader 加载所有的 ApplicationContextInitializerApplicationListener 以及 EnvironmentPostProcessor,然后刷新应用上下文,触发 Spring 的 Bean 加载和初始化过程。

关键源码

  • refreshContext 方法

    负责刷新应用上下文,包括触发 Spring 的生命周期回调,如 Bean 的加载、处理 @Configuration 注解等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
    try {
    context.registerShutdownHook();
    }
    catch (AccessControlException ex) {
    // Not allowed in some environments.
    }
    }
    refresh((ApplicationContext) context);
    }
  • refresh 方法

    1
    2
    3
    4
    protected void refresh(ConfigurableApplicationContext applicationContext) {
    // 调用 AbstractApplicationContext 的 refresh 方法
    applicationContext.refresh();
    }

触发自动配置

Spring Boot 的自动配置是通过 @EnableAutoConfiguration 注解实现的。AutoConfigurationImportSelector 会根据应用的依赖和 Environment 的属性选择性地导入配置类。

关键源码

  • AutoConfigurationImportSelector

    invokeBeanFactoryPostProcessors 阶段执行, 负责选择需要导入的自动配置类。

    1
    2
    3
    4
    5
    6
    7
    public class AutoConfigurationImportSelector extends ConfigurationClassImportSelector
    implements BeanFactoryAware, EnvironmentAware {
    @Override
    protected AnnotationMetadata getAnnotationMetadata() {
    return AnnotationMetadata.introspect(EnableAutoConfiguration.class);
    }
    }
  • spring.factories 文件

    META-INF/spring.factories 中,EnableAutoConfiguration 会指定所有可用的自动配置类。

    1
    2
    3
    4
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.MyAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    ...

处理 CommandLineRunnerApplicationRunner

在应用上下文刷新完成后,SpringApplication 会调用所有的 CommandLineRunnerApplicationRunner 接口的实现,用于在应用启动后执行特定的逻辑。

关键源码

  • callRunners 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
    if (this.mainApplicationClass != null) {
    Class<?> runnerType = CommandLineRunner.class;
    Collection<CommandLineRunner> runners = context.getBeansOfType(runnerType).values();
    for (CommandLineRunner runner : runners) {
    runner.run(args.getSourceArgs());
    }
    }
    }

启动 Web 服务器(如果是 Web 应用)

对于 Web 应用,Spring Boot 会自动启动嵌入式的 Web 服务器(如 Tomcat、Jetty 或 Undertow)。

启动流程:

在 AbstractApplicationContext的refresh的onRefresh阶段,会触发ServletWebServerApplicationContext的onRefresh方法,会去创建ServletWebServerFactory。最后,在 AbstractApplicationContext的refresh的最后阶段 finishRefresh() 时,会被触发 start方法,启动web服务器。

关键源码

  • WebServerStartStopLifecycle,他继承了SmartLifecycle,故能自动start与stop。他内部持有 WebServerServletWebServerApplicationContext

    1
    2
    3
    4
    5
    6
    7
    @Override
    public void start() {
    this.webServer.start();
    this.running = true;
    this.applicationContext
    .publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
    }
  • ServletWebServerApplicationContext

    管理 Web 服务器的生命周期。

    1
    2
    3
    4
    public class ServletWebServerApplicationContext extends GenericWebApplicationContext
    implements ConfigurableWebServerApplicationContext {
    // 实现细节
    }

总结

Spring Boot 的启动流程涉及以下主要步骤:

  1. 启动入口:通过调用 SpringApplication.run 启动应用。
  2. 环境准备:创建并配置 Environment,加载配置属性。
  3. 应用上下文创建:根据应用类型创建合适的 ApplicationContext 实例。
  4. 准备上下文:将环境配置、命令行参数、启动横幅等信息整合到应用上下文中,并通过监听器通知各个阶段的准备情况。
  5. 刷新上下文:触发 Spring 的 Bean 加载和初始化过程。
  6. 执行 Runner:调用 CommandLineRunnerApplicationRunner
  7. 启动 Web 服务器:如果是 Web 应用,启动嵌入式服务器。

通过以上步骤,Spring Boot 能够快速且自动化地启动和配置一个基于 Spring 的应用程序。理解这些流程和相关的源码,对于深入掌握 Spring Boot 的工作机制以及进行高级定制具有重要意义。