游乐游手机版
首页/AI教程/文章详情

从入门到放弃SpringBoot启动源码分析

时间:2026-06-06 17:26
前言 上一篇文章我们简单聊了MySQL的数据库连接过程,写完之后自己读了一遍,感觉分析得还不够透。很多细节其实并没有真正捋清楚,所以这次干脆从Springboot的源码入手,重新从头走一遍。 main 入口分析 package com springboot demo; import org myba

前言

上一篇文章我们简单聊了MySQL的数据库连接过程,写完之后自己读了一遍,感觉分析得还不够透。很多细节其实并没有真正捋清楚,所以这次干脆从Springboot的源码入手,重新从头走一遍。

main 入口分析

package com.springboot.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;

@MapperScan("com.springboot.demo.repository.dao")
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

一个最简单的Springboot项目,启动类里就一行代码。别小看这一行,背后藏着一整套路。我们就从这行开始拆。

SpringApplication::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));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

构造方法的主要工作就是初始化一堆成员变量:参数列表、应用类型、监听器等等。注意这里的 WebApplicationType.deduceFromClasspath(),它会根据classpath下有没有某些类来判断是web环境还是非web环境。

getSpringFactoriesInstances

private  Collection getSpringFactoriesInstances(Class type,
        Class[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

这个方法说白了就是根据传入的type,从 META-INF/spring.factories 里捞出一堆配置类名,然后一一实例化。过程中还做了去重和排序,挺严谨。

loadFactoryNames 负责读文件,createSpringFactoriesInstances 负责校验类型(必须是parameterTypes的子类)并通过反射创建实例。

\

SpringApplication::run

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Ja va main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection 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 方法里了。下面我们拆开看每一步。

StopWatch

一个计时器,先记下启动的时间点,最后用来算耗时。

变量初始化

各种临时变量的准备工作,后面会一步步用上。

configureHeadlessProperty

设置系统属性 ja va.awt.headless 为 true。说白了就是告诉操作系统:这个服务端程序不需要显示器、鼠标、键盘这些外设,如果代码里用到了图形资源,自己模拟就好了。

getRunListeners

spring.factories 中加载并实例化所有 SpringApplicationRunListener,这些监听器负责在启动的不同阶段广播事件。

prepareEnvironment

配置环境:加载配置文件、系统变量、命令行参数等,然后通过监听器广播环境准备完毕的事件。

printBanner

打印启动时那个标志性的横幅。

createApplicationContext

根据 webApplicationType 创建对应的 Spring 上下文实例。

exceptionReporters

初始化异常报告器,一旦启动过程中间出异常,这些报告器会负责输出友好信息。

prepareContext

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    Set sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

这一步做了几件事:

  • setEnvironment:把前面准备好的环境塞进上下文。
  • postProcessApplicationContext:默认空实现,留给子类扩展。
  • applyInitializers:调用之前实例化的 ApplicationContextInitializerinitialize 方法。
  • listeners.contextPrepared:通知监听器上下文准备完毕。
  • 注册两个单例 bean:springApplicationArgumentsspringBootBanner
  • load:通过 BeanDefinitionLoader 加载各种来源的 bean(注解、XML、package 等),最终全部注册到上下文中。
  • listeners.contextLoaded:通知监听器上下文加载完毕。

refreshContext

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();
        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);
        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);
            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            // Initialize message source for this context.
            initMessageSource();
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();
            // Initialize other special beans in specific context subclasses.
            onRefresh();
            // Check for listener beans and register them.
            registerListeners();
            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);
            // Last step: publish corresponding event.
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            // Destroy already created singletons to a void dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }
        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

refreshContext 是整个容器初始化的核心,调用了 AbstractApplicationContext.refresh()。下面拆解关键步骤:

  • prepareRefresh:设置启动时间、初始化占位符、校验必备属性等。
  • obtainFreshBeanFactory:通知子类刷新内部的 BeanFactory。
  • prepareBeanFactory:为 BeanFactory 做准备工作,比如设置 ClassLoader、注册一些特殊的 bean 等。
  • invokeBeanFactoryPostProcessors:重点来了——它会调用 ConfigurationClassPostProcessor.processConfigBeanDefinitions,这个方法是处理 @Configuration、@Component 等注解的入口。

咱们重点看看 processConfigBeanDefinitions 干了什么:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
            }
        }
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
    // Return immediately if no @Configuration classes were found
    if (configCandidates.isEmpty()) {
        return;
    }
    // ... 排序、设置BeanNameGenerator等
    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(...);
    Set candidates = new LinkedHashSet<>(configCandidates);
    Set alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        parser.parse(candidates);
        parser.validate();
        Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);
        // Read the model and create bean definitions based on its content
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(...);
        }
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
        candidates.clear();
        // ... 循环处理新发现的配置类
    } while (!candidates.isEmpty());
    // ... 注册 ImportRegistry
}

简单说,ConfigurationClassParser 解析 @Configuration、@ComponentScan、@Import 等注解,扫描出所有需要注册的类;ConfigurationClassBeanDefinitionReader 则负责把这些类转成 BeanDefinition 并注册到容器里。

后面还有 invokeBeanDefinitionRegistryPostProcessorsinvokeBeanFactoryPostProcessors 等回调,这里就不展开了。

  • registerBeanPostProcessors:注册用于拦截 bean 创建的处理器(比如 AOP 的切面)。
  • initMessageSource:初始化国际化消息源。
  • initApplicationEventMulticaster:初始化事件广播器。
  • onRefresh:模板方法,对于 web 环境(如 ServletWebServerApplicationContext)会在这里创建内嵌 Web 服务器。
  • registerListeners:把容器中实现了 ApplicationListener 的 bean 注册为监听器。
  • finishBeanFactoryInitialization:实例化所有非懒加载的单例 bean。像我们之前分析过的 DataSource,就是在这个阶段被创建的。

finishRefresh

结束上下文的刷新,发布 ContextRefreshedEvent 事件。

callRunners

如果容器中定义了 ApplicationRunnerCommandLineRunner 类型的 bean,就会调用它们的 run 方法,让应用启动后执行一些自定义逻辑。

总结

到这里,Springboot 的启动过程就串起来了。整体脉络可以概括为:

  • 初始化环境
  • 加载默认配置
  • 初始化各类监听器和事件
  • 创建上下文
  • 往上下文中添加默认的 bean
  • 扫描各类注解(@Component、@Bean、@Import 等),注册所有 bean 定义
  • 实例化所有单例 bean
  • 启动完毕,执行 Runner

整个过程虽然代码量大,但每一步都职责分明。理解了这个流程,以后遇到启动相关的问题,心里就有底了。

来源:https://developer.aliyun.com/article/704370
上一篇OpenClaw智能助理平台云端部署完整流程六大核心场景 下一篇AI智能体时代OPC中国人才生态:政府高校园区三驾马车
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
年最新JetBrains AI助手Windows本地详细安装配置教程(含下载与环境要求)
AI教程 · 2026-07-03

年最新JetBrains AI助手Windows本地详细安装配置教程(含下载与环境要求)

JetBrainsAIAssistant可在Windows上通过IDE内置市场或离线包安装,需匹配新版JetBrainsIDE、账号登录与稳定网络。配置时应关注版本兼容、隐私设置、项目索引、快捷键和代码提交前复核,避免上传密钥与敏感业务资料。

Amazon Q Developer新手安装指南:从下载到首次运行的保姆级教程
AI教程 · 2026-07-03

Amazon Q Developer新手安装指南:从下载到首次运行的保姆级教程

AmazonQDeveloper可为编码、调试、解释项目和生成测试提供辅助。安装前需确认账号、开发环境和插件来源,按IDE或命令行路径完成配置,并在首次运行时注意权限、数据与项目安全。

Amazon Q Developer安装失败怎么办?报错日志排查与升级回滚方案
AI教程 · 2026-07-03

Amazon Q Developer安装失败怎么办?报错日志排查与升级回滚方案

AmazonQDeveloper安装失败通常与版本兼容、网络连接、身份登录、插件残留或权限配置有关。排查时应先确认环境,再查看IDE与终端日志,必要时采用清理重装、固定版本升级或回滚方案。

Amazon Q Developer本地模型运行:下载、路径与性能优化
AI教程 · 2026-07-03

Amazon Q Developer本地模型运行:下载、路径与性能优化

AmazonQDeveloper以云端能力为主,本地模型方案更适合离线补充、代码检索和私有环境辅助。配置时需确认版本、模型来源、路径权限、硬件资源与IDE集成方式,并通过量化、上下文控制和缓存策略优化性能。

Amazon Q Developer插件安装全流程:浏览器编辑器扩展市场配置
AI教程 · 2026-07-03

Amazon Q Developer插件安装全流程:浏览器编辑器扩展市场配置

AmazonQDeveloper可在浏览器控制台、VSCode、JetBrains等环境中辅助写代码、解释项目和生成测试。安装前需确认账号权限、编辑器版本与网络环境,配置时重点关注登录授权、工作区信任、数据权限和团队使用规范。