Spring Boot启动流程解析

0 阅读9分钟

Spring Boot启动流程解析

一、概述

当我们写下这行代码启动一个Spring Boot应用时:

SpringApplication.run(Application.class, args);

看似轻描淡写的一行,实则暗藏玄机。就在这短短的一瞬间,Spring Boot已经完成了一系列精密而复杂的初始化工作——从推断应用类型到加载配置文件,从创建上下文到实例化Bean,每一步都环环相扣。本文将带你深入源码,揭开Spring Boot启动流程的神秘面纱。

二、启动流程总览

Spring Boot的启动流程宛如一场精心编排的交响乐,可以分为以下几个核心乐章:

┌─────────────────────────────────────────────────────────────────┐
│                    Spring Boot 启动流程                          │
└─────────────────────────────────────────────────────────────────┘
1. 创建 SpringApplication 对象
   ├── 推断应用类型(Servlet/Reactive/None)
   ├── 加载初始化器(ApplicationContextInitializer)
   └── 加载监听器(ApplicationListener)
2. 执行 run() 方法
   ├── 准备阶段
   │   ├── 获取并启动监听器
   │   ├── 准备环境变量
   │   └── 打印Banner
   ├── 创建上下文
   │   ├── 创建 ApplicationContext
   │   ├── 准备上下文
   │   └── 执行初始化器
   ├── 刷新上下文
   │   ├── Bean定义加载
   │   ├── Bean实例化
   │   └── 自动装配
   └── 运行阶段
       ├── 执行Runner
       └── 发布启动完成事件

三、SpringApplication构造过程

3.1 构造方法源码

一切始于 SpringApplication的构造。当我们调用 run()方法时,首先会创建一个 SpringApplication实例:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 1. 推断应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 2. 加载初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 3. 加载监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 4. 推断主类
    this.mainApplicationClass = deduceMainApplicationClass();
}

构造方法虽然简洁,却完成了四项关键任务。

3.2 推断应用类型

Spring Boot如何知道我们的应用是传统Web应用、响应式应用,还是非Web应用?答案藏在 WebApplicationType.deduceFromClasspath()方法中:

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) 
            && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

Spring Boot通过检测类路径上是否存在特定类来推断应用类型,这种设计既巧妙又实用:

应用类型条件
SERVLET存在Servlet相关类,传统Web应用
REACTIVE存在WebFlux但不存在WebMVC,响应式应用
NONE不存在Web相关类,非Web应用

3.3 加载初始化器和监听器

初始化器和监听器是Spring Boot扩展机制的核心。Spring Boot通过 getSpringFactoriesInstances()方法加载它们:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 使用 SpringFactoriesLoader 加载(Spring Boot 2.4+ 同时支持 spring.factories 和新的 imports 机制)
    Set<String> names = new LinkedHashSet<>(
        SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 创建实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

注意 :Spring Boot 2.4及更高版本引入了新的配置文件加载机制。虽然 spring.factories仍然可用,但自动配置类建议迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中。

常见的初始化器:

初始化器作用
ConfigurationWarningsApplicationContextInitializer报告配置警告
ContextIdApplicationContextInitializer设置上下文ID
DelegatingApplicationContextInitializer委托给环境属性配置的初始化器
ServerPortInfoApplicationContextInitializer设置服务端口信息

常见的监听器:

监听器作用
ConfigDataEnvironmentPostProcessor加载配置文件(Spring Boot 2.4+替代了原有的ConfigFileApplicationListener)
AnsiOutputApplicationListener配置ANSI输出
LoggingApplicationListener配置日志系统
ClasspathLoggingApplicationListener记录类路径信息

四、run()方法核心流程

4.1 run()方法源码

run()方法是Spring Boot启动的核心引擎,它将各个阶段串联成一个完整的启动流程:

public ConfigurableApplicationContext run(String... args) {
    // 1. 创建计时器
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 创建 BootstrapContext(Spring Boot 2.4+引入,用于早期阶段的上下文共享)
    DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();  
    // 2. 获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
     try {
        // 3. 准备环境
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 4. 打印Banner
        Banner printedBanner = printBanner(environment);
        // 5. 创建ApplicationContext
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 6. 准备上下文
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 7. 刷新上下文
        refreshContext(context);
        // 8. 刷新后处理
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
         // 9. 发布启动完成事件
        listeners.started(context);
        // 10. 执行Runner
        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;
}

4.2 各阶段详解

阶段一:获取并启动监听器

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
        getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
        this.applicationStartup);
}

这一阶段主要获取 EventPublishingRunListener,它就像启动流程的"广播员",负责发布各种启动事件:

事件触发时机
ApplicationStartingEvent启动开始时
ApplicationEnvironmentPreparedEvent环境准备完成时
ApplicationContextInitializedEvent上下文初始化完成时
ApplicationPreparedEvent上下文准备完成时
ApplicationStartedEvent上下文刷新完成时
ApplicationReadyEvent应用就绪时

阶段二:准备环境

环境准备是启动流程中至关重要的一环,它决定了应用后续如何读取配置:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 创建环境对象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 配置属性源
    ConfigurationPropertySources.attach(environment);
    // 发布环境准备事件
    listeners.environmentPrepared(bootstrapContext, environment);
    // 绑定到SpringApplication
    DefaultPropertiesPropertySource.moveToEnd(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = convertEnvironment(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

环境准备过程如同搭建一座桥梁,连接配置源与应用:

┌─────────────────────────────────────────────────────────────────┐
│                    Environment 创建过程                          │
└─────────────────────────────────────────────────────────────────┘
创建 Environment
       │
       ▼
┌─────────────────────────────┐
│  Servlet环境:                │
│  StandardServletEnvironment │
│                             │
│  Reactive环境:               │
│  StandardReactiveWebEnvironment │
│                             │
│  非Web环境:                  │
│  StandardEnvironment        │
└─────────────────────────────┘
       │
       ▼
┌─────────────────────────────┐
│  配置 PropertySources       │
│  ├── CommandLine            │
│  ├── SystemProperties       │
│  ├── SystemEnvironment      │
│  └── application.yml/properties │
└─────────────────────────────┘
       │
       ▼
┌─────────────────────────────┐
│  发布环境准备事件            │
│  ApplicationEnvironmentPreparedEvent │
└─────────────────────────────┘

阶段三:打印Banner

启动Banner是Spring Boot的标志性特征之一,那个醒目的Spring Logo总能让人眼前一亮:

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader :
        new DefaultResourceLoader(getClassLoader());
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
        resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

Banner加载顺序如下(图片优先于文本):

  1. banner.gif/banner.jpg/banner.png(图片文件优先)
  2. banner.txt(文本文件)
  3. classpath下的 banner.txt
  4. 默认Spring Boot Banner

阶段四:创建ApplicationContext

根据应用类型,Spring Boot会创建不同的ApplicationContext:

protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
}
应用类型上下文类型
SERVLETAnnotationConfigServletWebServerApplicationContext
REACTIVEAnnotationConfigReactiveWebServerApplicationContext
NONEAnnotationConfigApplicationContext

阶段五:准备上下文

上下文准备阶段是连接配置与Bean定义的关键环节:

private void prepareContext(DefaultBootstrapContext bootstrapContext, 
        ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner banner) {
    // 设置环境
    context.setEnvironment(environment);
    // 后置处理上下文
    postProcessApplicationContext(context);
    // 执行初始化器
    applyInitializers(context);
    // 发布上下文准备事件
    listeners.contextPrepared(context);
    // 注册单例Bean
    context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
    if (banner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", banner);
    }
    // 加载主配置类
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    // 发布上下文加载完成事件
    listeners.contextLoaded(context);
}

初始化器执行过程:

protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
            initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, 
            "Unable to call initializer.");
        initializer.initialize(context);
    }
}

阶段六:刷新上下文

refresh()方法是Spring框架的灵魂所在,定义在 AbstractApplicationContext中。它将Bean定义转化为真正的Bean实例:

private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        shutdownHook.registerApplicationContext(context);
    }
    refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
    applicationContext.refresh();
}

refresh()方法的十二个步骤,每一步都承载着特定的使命:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        // 1. 准备刷新上下文
        prepareRefresh();
        // 2. 获取BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 3. 准备BeanFactory
        prepareBeanFactory(beanFactory);
        try {
            // 4. 后置处理BeanFactory
            postProcessBeanFactory(beanFactory);
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 5. 执行BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            // 6. 注册BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();
            // 7. 初始化消息源
            initMessageSource();
            // 8. 初始化事件派发器
            initApplicationEventMulticaster();
            // 9. 初始化其他特殊Bean
            onRefresh();
            // 10. 注册监听器
            registerListeners();
            // 11. 实例化所有非懒加载单例Bean
            finishBeanFactoryInitialization(beanFactory);
            // 12. 完成刷新
            finishRefresh();
        } catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        } finally {
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

阶段七:执行Runner

当所有准备工作就绪,Spring Boot会执行用户自定义的Runner:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Runner> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Runner runner : runners) {
        if (runner instanceof ApplicationRunner) {
            ((ApplicationRunner) runner).run(args);
        }
        if (runner instanceof CommandLineRunner) {
            ((CommandLineRunner) runner).run(args.getSourceArgs());
        }
    }
}

五、完整启动时序图

下面的时序图展示了启动过程中各组件之间的协作关系:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│SpringApp │     │Listeners │     │Environment│    │Context   │     │BeanFactory│
└────┬─────┘     └────┬─────┘     └────┬─────┘     └────┬─────┘     └────┬─────┘
     │                │                │                │                │
     │   starting()   │                │                │                │
     │───────────────>│                │                │                │
     │                │                │                │                │
     │                │  createEnvironment()            │                │
     │────────────────────────────────>│                │                │
     │                │                │                │                │
     │                │environmentPrepared()            │                │
     │───────────────>│                │                │                │
     │                │                │                │                │
     │                │                │  create()      │                │
     │────────────────────────────────────────────────>│                │
     │                │                │                │                │
     │                │contextPrepared()                │                │
     │───────────────>│                │                │                │
     │                │                │                │                │
     │                │                │  load()        │                │
     │────────────────────────────────────────────────>│                │
     │                │                │                │                │
     │                │contextLoaded() │                │                │
     │───────────────>│                │                │                │
     │                │                │                │                │
     │                │                │                │  refresh()     │
     │────────────────────────────────────────────────>│───────────────>│
     │                │                │                │                │
     │                │                │                │<───────────────│
     │                │                │<───────────────────────────────│
     │                │                │                │                │
     │                │started()       │                │                │
     │───────────────>│                │                │                │
     │                │                │                │                │
     │                │running()       │                │                │
     │───────────────>│                │                │                │
     │                │                │                │                │
     ▼                ▼                ▼                ▼                ▼

六、自定义启动扩展点

Spring Boot提供了丰富的扩展点,让我们可以在启动流程的关键节点注入自定义逻辑。

6.1 ApplicationContextInitializer

在上下文刷新之前执行,可用于修改ApplicationContext:

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializer executed!");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        environment.addActiveProfile("dev");
    }
}

注册方式一:代码注册

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.addInitializers(new MyApplicationContextInitializer());
        app.run(args);
    }
}

注册方式二:配置文件注册(Spring Boot 2.4+推荐使用imports文件)

META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports文件中添加:

com.example.MyApplicationContextInitializer

6.2 ApplicationListener

监听应用启动过程中的各种事件,是观察启动流程的绝佳窗口:

public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("Application started at: " + event.getTimestamp());
    }
}

6.3 CommandLineRunner 和 ApplicationRunner

当应用启动完成后,如果你需要执行一些初始化任务,这两个接口是你的最佳选择:

@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner executed with args: " + Arrays.toString(args));
    }
}
@Component
@Order(2)
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner executed");
        System.out.println("Option names: " + args.getOptionNames());
        System.out.println("Non-option args: " + args.getNonOptionArgs());
    }
}

ApplicationRunnerCommandLineRunner的区别在于:前者提供了更丰富的参数解析能力,可以区分选项参数和非选项参数。

6.4 SmartLifecycle

当你需要在上下文刷新后启动某些后台服务,并在应用关闭时优雅停止,SmartLifecycle是不二之选:

@Component
public class MySmartLifecycle implements SmartLifecycle {
    private boolean running = false;
    @Override
    public void start() {
        System.out.println("SmartLifecycle starting...");
        running = true;
    }
    @Override
    public void stop() {
        System.out.println("SmartLifecycle stopping...");
        running = false;
    }
    @Override
    public boolean isRunning() {
        return running;
    }
    @Override
    public int getPhase() {
        return 0;
    }
    @Override
    public boolean isAutoStartup() {
        return true;
    }
}

七、启动流程调试技巧

7.1 启用启动日志

想要一窥启动过程的细节?开启调试日志是最直接的方式:

logging.level.org.springframework.boot=DEBUG
logging.level.org.springframework.context=DEBUG

7.2 启动时间分析

如果你的应用启动缓慢,可以使用 BufferingApplicationStartup来分析每个步骤的耗时:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setApplicationStartup(new BufferingApplicationStartup(10000));
        ConfigurableApplicationContext context = app.run(args);
        BufferingApplicationStartup startup = (BufferingApplicationStartup) 
            context.getBeanFactory().getSingleton("springApplicationStartup");
        startup.getBufferedSteps().forEach(step -> {
            System.out.println(step.getName() + ": " + step.getDuration().toMillis() + "ms");
        });
    }
}

7.3 使用Spring Boot Actuator

Spring Boot Actuator提供了更专业的启动分析能力:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=startup

启动后访问 /actuator/startup,你将获得一份详尽的启动报告。

八、总结

Spring Boot的启动流程,从创建 SpringApplication到返回一个完整的 ApplicationContext,经历了以下核心步骤:

步骤核心操作关键类
1创建SpringApplicationSpringApplication
2推断应用类型WebApplicationType
3加载初始化器和监听器SpringFactoriesLoader
4准备环境ConfigurableEnvironment
5创建ApplicationContextApplicationContextFactory
6执行初始化器ApplicationContextInitializer
7刷新上下文AbstractApplicationContext.refresh()
8执行RunnerCommandLineRunner/ApplicationRunner

深入理解Spring Boot的启动流程,不仅能帮助我们正确使用各种扩展点,更能让我们在遇到启动问题时快速定位根源,在性能优化时有的放矢。这正是掌握框架原理的价值所在——知其然,更知其所以然。

参考资料:

原文链接:Spring Boot启动流程解析