Spring Boot启动流程解析

Spring Boot启动流程解析

一、概述

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

arduino 复制代码
SpringApplication.run(Application.class, args);

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

二、启动流程总览

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

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

三、SpringApplication构造过程

3.1 构造方法源码

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

java 复制代码
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()方法中:

java 复制代码
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()方法加载它们:

java 复制代码
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启动的核心引擎,它将各个阶段串联成一个完整的启动流程:

java 复制代码
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 各阶段详解

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

java 复制代码
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 应用就绪时

阶段二:准备环境

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

java 复制代码
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;
}

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

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

阶段三:打印Banner

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

java 复制代码
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:

java 复制代码
protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
}
应用类型 上下文类型
SERVLET AnnotationConfigServletWebServerApplicationContext
REACTIVE AnnotationConfigReactiveWebServerApplicationContext
NONE AnnotationConfigApplicationContext

阶段五:准备上下文

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

java 复制代码
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);
}

初始化器执行过程:

java 复制代码
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实例:

java 复制代码
private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        shutdownHook.registerApplicationContext(context);
    }
    refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
    applicationContext.refresh();
}

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

java 复制代码
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:

java 复制代码
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());
        }
    }
}

五、完整启动时序图

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

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

六、自定义启动扩展点

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

6.1 ApplicationContextInitializer

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

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

注册方式一:代码注册

java 复制代码
@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

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

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

6.3 CommandLineRunner 和 ApplicationRunner

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

java 复制代码
@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是不二之选:

java 复制代码
@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 启用启动日志

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

properties 复制代码
logging.level.org.springframework.boot=DEBUG
logging.level.org.springframework.context=DEBUG

7.2 启动时间分析

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

java 复制代码
@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提供了更专业的启动分析能力:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
properties 复制代码
management.endpoints.web.exposure.include=startup

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

八、总结

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

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

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

参考资料:

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

相关推荐
王码码20352 小时前
Go语言的测试:从单元测试到集成测试
后端·golang·go·接口
王码码20352 小时前
Go语言中的测试:从单元测试到集成测试
后端·golang·go·接口
嵌入式×边缘AI:打怪升级日志2 小时前
使用JsonRPC实现前后台
前端·后端
小码哥_常3 小时前
从0到1:Spring Boot 中WebSocket实战揭秘,开启实时通信新时代
后端
lolo大魔王3 小时前
Go语言的异常处理
开发语言·后端·golang
IT_陈寒6 小时前
Python多进程共享变量那个坑,我差点没爬出来
前端·人工智能·后端
码事漫谈6 小时前
2026软考高级·系统架构设计师备考指南
后端
AI茶水间管理员7 小时前
如何让LLM稳定输出 JSON 格式结果?
前端·人工智能·后端
其实是白羊7 小时前
我用 Vibe Coding 搓了一个 IDEA 插件,复制URI 再也不用手动拼了
后端·intellij idea
用户8356290780517 小时前
Python 操作 Word 文档节与页面设置
后端·python