游乐游手机版
首页/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中国人才生态:政府高校园区三驾马车
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

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

同类最新

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

更多
阿里云OpenClaw官方镜像六大场景3分钟开箱即用指南
AI教程 · 2026-06-06

阿里云OpenClaw官方镜像六大场景3分钟开箱即用指南

先聊聊OpenClaw到底是什么,以及它为什么值得关注。作为阿里云推出的智能助理平台,OpenClaw基于通义千问大模型深度定制,目标很明确:为开发者、创作者、运营者提供一站式的AI赋能解决方案。下面直接切入正题,看看它的六大核心场景。 OpenClaw 智能助理:六大核心场景赋能开发者高效成长 O

Moltbot Clawdbot与飞书机器人接入实践
AI教程 · 2026-06-06

Moltbot Clawdbot与飞书机器人接入实践

简单认识一下 Clawdbot 最近 AI 圈被一款名为 Clawdbot 的产品刷屏了。不管是在国内技术社区,还是刷 TG、X 的时候,几乎都能看到有人在讨论它。 看了一下官方文档,Clawdbot 本质上就是一个偏“个人智能助手”的东西。不过它并不是单独开一个网页给我们用,而是可以直接接入我们平

SpringAI与ONNX打造免费离线向量引擎
AI教程 · 2026-06-06

SpringAI与ONNX打造免费离线向量引擎

前段时间尝试了一个很有意思的项目——原本只是想在 Spring AI 项目中顺手集成 ONNX 模型,结果一上手就停不下来,直接调试到凌晨两点,边调边感慨:整个过程也太丝滑流畅了。 今天就来深入聊聊这件事:如何在 Spring AI 中使用 ONNX 向量模型,实现本地化的文本嵌入能力。 如果你之前

AI智能体技能完全指南:让你的AI助手拥有超能力
AI教程 · 2026-06-06

AI智能体技能完全指南:让你的AI助手拥有超能力

引言:AI Agent 的能力边界在哪里?你的AI编程助手可以编写代码,但它是否真正理解你公司的独特工作流程?能否自动处理你的CI CD流水线?又是否熟悉你日常使用的那些特定工具与API接口?AI Agent Skills正是为解决这一痛点而诞生的——它们作为可复用的能力模块,能够将通用型AI助手转

AI编程神器狂揽34k星与Claude Code和Codex绝配
AI教程 · 2026-06-06

AI编程神器狂揽34k星与Claude Code和Codex绝配

CC Switch:一站式AI编程工具管理神器 今天要介绍的这款实用小工具,名字叫作CC Switch。它是一款跨平台的桌面“All-in-One”助手,专门用于管理主流的AI编程开发工具。目前该项目在GitHub上已经获得了34k+ star,关注度非常高。它的核心卖点很直接:提供一个可视化操作界