请添加图片描述

👋 欢迎阅读《Java面试200问》系列博客!

🚀大家好,我是Jinkxs,一名热爱Java、深耕技术一线的开发者。在准备和参与了数十场Java面试后,我深知面试不仅是对知识的考察,更是对理解深度与表达能力的综合检验。

✨本系列将带你系统梳理Java核心技术中的高频面试题,从源码原理到实际应用,从常见陷阱到大厂真题,每一篇文章都力求深入浅出、图文并茂,帮助你在求职路上少走弯路,稳拿Offer!

🔍今天我们要聊的是:《Spring Boot 启动流程源码分析》。准备好了吗?Let’s go!


Spring Boot 启动流程源码分析:从 main 到“Hello World”的奇幻漂流


引言:一个 main 方法的“野心”

还记得你第一次写 Spring Boot “Hello World” 时的场景吗?

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

就这么一行 SpringApplication.run(),一个 Web 服务器就启动了,还自动配置了 Tomcat、Spring MVC、甚至数据库连接池!

你有没有想过:这行代码背后,到底发生了什么?

今天,我们就来一场“代码探险”,深入 Spring Boot 的源码,看看这个看似简单的 main 方法,是如何一步步“点燃”整个应用的。

准备好你的 IDE 和咖啡,我们要“钻”进 SpringApplication.run() 了!


第一站:SpringApplication.run() —— 启动的“总开关”

1.1 run() 方法源码初探

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return new SpringApplication(primarySource).run(args);
}

就两行?别急,这就像“薛定谔的猫”——表面平静,内里波涛汹涌。

它做了两件事:

  1. new SpringApplication(primarySource):创建一个 SpringApplication 实例,准备启动环境
  2. .run(args):执行真正的启动流程

第二站:new SpringApplication() —— 初始化“启动器”

2.1 构造方法源码

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 🔍 推断 Web 应用类型:REACTIVE, SERVLET, NONE
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 🧩 加载 ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    
    // 🚨 加载 ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 🎯 推断主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

我们来逐个“解剖”:


2.2 推断 Web 应用类型:WebApplicationType.deduceFromClasspath()

Spring Boot 很“聪明”,它会根据类路径下的依赖自动判断应用类型:

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)
        && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
        && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", null)) {
        return WebApplicationType.REACTIVE;
    }
    if (ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
        && !ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)) {
        return WebApplicationType.SERVLET;
    }
    return WebApplicationType.NONE;
}
类路径存在 应用类型
DispatcherServlet SERVLET(传统 Web)
DispatcherHandler 且无 DispatcherServlet REACTIVE(响应式)
都没有 NONE(非 Web)

2.3 加载 ApplicationContextInitializer

这些是在 Spring 容器刷新之前执行的初始化器。

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

getSpringFactoriesInstances() 会从 META-INF/spring.factories 中加载:

# spring-boot-autoconfigure/META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

这些初始化器负责:

  • 提醒配置错误
  • 设置应用 ID
  • 代理其他初始化器
  • 暴露服务器端口信息

2.4 加载 ApplicationListener

事件监听器,用于响应 Spring 的各种事件(如 ApplicationStartedEventApplicationReadyEvent)。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

同样从 spring.factories 加载:

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener

2.5 推断主配置类:deduceMainApplicationClass()

它会遍历调用栈,找到包含 main 方法的类:

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    } catch (ClassNotFoundException ex) {
        // 忽略
    }
    return null;
}

第三站:.run(args) —— 启动的“核心引擎”

3.1 run() 方法主流程

public ConfigurableApplicationContext run(String... args) {
    // 1. 创建并启动计时器
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // 2. 声明上下文对象
    ConfigurableApplicationContext context = null;
    
    // 3. 配置 Headless 模式
    configureHeadlessProperty();
    
    // 4. 获取并启动 SpringApplicationRunListeners(主要是 EventPublishingRunListener)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    
    try {
        // 5. 封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 6. 准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        
        // 7. 配置忽略 BeanInfo
        configureIgnoreBeanInfo(environment);
        
        // 8. 打印 Banner
        Banner printedBanner = printBanner(environment);
        
        // 9. 创建应用上下文
        context = createApplicationContext();
        
        // 10. 准备异常报告器
        ExceptionReporters exceptionReporters = getSpringFactoriesInstances(ExceptionReporter.class,
                new Class<?>[] { ConfigurableApplicationContext.class }, context);
        
        // 11. 准备应用上下文
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        // 12. 刷新上下文(核心!)
        refreshContext(context);
        
        // 13. 刷新后处理
        afterRefresh(context, applicationArguments);
        
        // 14. 停止计时器,输出启动时间
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        
        // 15. 发布应用已启动事件
        listeners.started(context);
        
        // 16. 调用 ApplicationRunner 和 CommandLineRunner
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 17. 发布应用准备就绪事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    
    // 18. 返回应用上下文
    return context;
}

第四站:prepareEnvironment() —— 环境准备

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    
    // 1. 根据 webApplicationType 创建对应的 Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    
    // 2. 配置环境(如激活 profile)
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    
    // 3. 发布环境准备事件
    listeners.environmentPrepared(environment);
    
    // 4. 绑定 environment 到 SpringApplication
    bindToSpringApplication(environment);
    
    // 5. 如果是 web 环境且未配置无主配置类,则转换环境
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
            .convertEnvironmentIfNecessary(environment, deduceInitializers());
    }
    
    // 6. 配置 PropertySources 的顺序
    ConfigurationPropertySources.attach(environment);
    
    return environment;
}

第五站:createApplicationContext() —— 创建上下文

根据 webApplicationType 创建不同类型的 ApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context." +
                        "AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context." +
                        "AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context." +
                        "annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

第六站:prepareContext() —— 准备上下文

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    
    // 1. 设置环境
    context.setEnvironment(environment);
    
    // 2. 执行 ApplicationContextInitializers
    postProcessApplicationContext(context);
    applyInitializers(context);
    
    // 3. 发布上下文准备事件
    listeners.contextPrepared(context);
    
    // 4. 记录启动日志
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    
    // 5. 添加主配置类作为 Bean 定义
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    
    // 6. 发布上下文加载事件
    listeners.contextLoaded(context);
}

第七站:refreshContext() —— 刷新上下文(最核心!)

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // 不处理
        }
    }
}

// 实际调用父类 AbstractApplicationContext 的 refresh()
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新:设置启动时间、激活标志等
        prepareRefresh();

        // 2. 获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3. 准备 BeanFactory:设置类加载器、表达式解析器、注册默认的 BeanPostProcessor 等
        prepareBeanFactory(beanFactory);

        try {
            // 4. 子类后处理 BeanFactory
            postProcessBeanFactory(beanFactory);

            // 5. 执行 BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6. 注册 BeanPostProcessor
            registerBeanPostProcessors(beanFactory);

            // 7. 初始化 MessageSource
            initMessageSource();

            // 8. 初始化 ApplicationEventMulticaster
            initApplicationEventMulticaster();

            // 9. 子类特殊初始化
            onRefresh();

            // 10. 注册监听器
            registerListeners();

            // 11. 实例化所有非懒加载的单例 Bean
            finishBeanFactoryInitialization(beanFactory);

            // 12. 完成刷新:发布事件、初始化 LifecycleProcessor
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context refresh", ex);
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}

第八站:自动配置的“引爆点”——invokeBeanFactoryPostProcessors

refresh() 中,invokeBeanFactoryPostProcessors(beanFactory) 是自动配置的“引爆点”。

它会执行 ConfigurationClassPostProcessor,而这个处理器会处理 @Configuration 类,最终触发 @EnableAutoConfiguration

回忆一下 @EnableAutoConfiguration 的源码:

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

AutoConfigurationImportSelector 会通过 spring.factories 加载所有自动配置类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

然后通过 @ConditionalOnXXX 条件注解过滤,最终导入符合条件的配置类。


启动流程全景图

graph TD
    A[main()] --> B[new SpringApplication()]
    B --> C1[推断 Web 类型]
    B --> C2[加载 Initializers]
    B --> C3[加载 Listeners]
    B --> C4[推断主类]
    
    B --> D[run(args)]
    D --> E[启动计时器]
    D --> F[获取 RunListeners]
    D --> G[准备 Environment]
    D --> H[创建 ApplicationContext]
    D --> I[准备 Context]
    D --> J[refreshContext]
    J --> K[prepareRefresh]
    J --> L[obtainFreshBeanFactory]
    J --> M[prepareBeanFactory]
    J --> N[postProcessBeanFactory]
    J --> O[invokeBeanFactoryPostProcessors]
    O --> P[处理 @EnableAutoConfiguration]
    P --> Q[加载 spring.factories]
    P --> R[条件过滤 AutoConfig]
    J --> S[registerBeanPostProcessors]
    J --> T[initMessageSource]
    J --> U[initApplicationEventMulticaster]
    J --> V[onRefresh]
    V --> W[创建内嵌 Web 服务器]
    J --> X[registerListeners]
    J --> Y[finishBeanFactoryInitialization]
    Y --> Z[实例化所有非懒加载 Bean]
    J --> AA[finishRefresh]
    AA --> AB[发布 ContextRefreshedEvent]
    
    D --> AC[afterRefresh]
    D --> AD[callRunners]
    D --> AE[发布 running 事件]
    D --> AF[返回 ApplicationContext]

面试题与答案

❓ 问:SpringApplication.run() 做了哪些事?

  1. 创建 SpringApplication 实例,初始化环境、加载监听器和初始化器
  2. 执行 run() 方法:
    • 准备环境
    • 创建应用上下文
    • 准备上下文(加载主配置类)
    • 刷新上下文(核心,触发自动配置)
    • 调用 ApplicationRunner/CommandLineRunner
    • 返回 ApplicationContext

❓ 问:Spring Boot 如何推断 Web 应用类型?


通过 WebApplicationType.deduceFromClasspath() 方法,检查类路径中是否存在特定类:

  • DispatcherServletSERVLET
  • DispatcherHandler 且无 DispatcherServletREACTIVE
  • 都没有 → NONE

❓ 问:spring.factories 的作用是什么?


它是 Spring Boot 的扩展机制,用于注册自动配置类、监听器、初始化器等。Spring Boot 启动时会加载 META-INF/spring.factories 中的配置,实现“自动发现”。

❓ 问:自动配置是如何生效的?

  1. @EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector
  2. AutoConfigurationImportSelectorspring.factories 中加载所有自动配置类
  3. 使用 @ConditionalOnXXX 注解过滤,只保留符合条件的配置类
  4. 这些配置类被加载为 @Configuration,Spring 容器根据条件创建相应的 Bean

❓ 问:refresh() 方法中最重要的步骤是什么?


invokeBeanFactoryPostProcessors(beanFactory) 最关键,因为它触发了 ConfigurationClassPostProcessor,进而处理 @EnableAutoConfiguration,加载自动配置类,是“自动配置”的起点。

❓ 问:如何自定义启动过程?

  • 实现 ApplicationContextInitializer 并在 spring.factories 中注册
  • 实现 ApplicationListener 监听启动事件
  • 使用 SpringApplicationRunListener 监控启动全过程
  • main 方法中自定义 SpringApplication 实例

总结

阶段 关键动作
初始化 推断类型、加载监听器/初始化器
run() 计时、准备环境、创建上下文
prepareContext 设置环境、执行初始化器、加载主类
refreshContext 刷新容器、执行自动配置、实例化 Bean
启动完成 调用 Runner、发布事件

Spring Boot 的启动流程,就像一场精心编排的“交响乐”,从 main 方法开始,一步步构建环境、加载配置、启动容器,最终让应用“活”起来。

理解这个流程,不仅能让你在面试中游刃有余,更能让你在实际开发中“知其然,更知其所以然”。

下次当你看到 SpringApplication.run() 时,不妨在心里默念:“启动,开始!”


🎯 总结一下:

本文深入探讨了《Spring Boot 启动流程源码分析》,从原理到实践,解析了面试中常见的考察点和易错陷阱。掌握这些内容,不仅能应对面试官的连环追问,更能提升你在实际开发中的技术判断力。

🔗 下期预告:我们将继续深入Java面试核心,带你解锁《Spring Boot 中的 Starter 原理与自定义 Starter》 的关键知识点,记得关注不迷路!

💬 互动时间:你在面试中遇到过类似问题吗?或者对本文内容有疑问?欢迎在评论区留言交流,我会一一回复!

如果你觉得这篇文章对你有帮助,别忘了 点赞 + 收藏 + 转发,让更多小伙伴一起进步!我们下一篇见 👋

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐