小白已经掌握了自动配置的魔法,但他对SpringBoot应用的启动过程仍然充满好奇:当我们运行
main
方法时,背后到底发生了哪些事情?今天,我们将揭开SpringBoot启动流程的神秘面纱。
📖 故事序幕:启动魔法引擎
在魔法学院的引擎室内,老巫师指着一台巨大的魔法引擎对小白说:"这就是SpringBoot的启动引擎,它负责启动整个魔法世界。让我们一步步拆解它的工作原理。"
🧙 第一幕:启动的起点------SpringApplication初始化
📚 故事场景:魔法引擎的组装
老巫师开始讲解:"首先,我们需要组装魔法引擎的各个部件。"
java
// 小白的启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// 这就是启动魔法的咒语!
SpringApplication.run(DemoApplication.class, args);
}
}
严谨推理:
当调用SpringApplication.run(DemoApplication.class, args)
时,SpringBoot启动流程开始。首先会创建一个SpringApplication
实例,并进行初始化。

魔法实践(源码分析):
java
public class SpringApplication {
// 启动应用的静态方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 创建一个SpringApplication实例并运行
return new SpringApplication(primarySources).run(args);
}
// 构造方法 - 初始化魔法引擎
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
// 设置主配置类(就是我们的DemoApplication)
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 第一步:推断应用类型
// 根据classpath中的类判断是Web应用还是普通应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 第二步:设置初始化器(ApplicationContextInitializer)
// 从META-INF/spring.factories中加载
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 第三步:设置监听器(ApplicationListener)
// 从META-INF/spring.factories中加载
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 第四步:推断主配置类(包含main方法的类)
this.mainApplicationClass = deduceMainApplicationClass();
}
// 推断应用类型的魔法
static WebApplicationType deduceFromClasspath() {
// 如果存在Spring WebFlux相关类,则是REACTIVE应用
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 如果不存在Servlet相关类,则是普通应用
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 默认是SERVLET Web应用(Spring MVC)
return WebApplicationType.SERVLET;
}
}
🔮 第二幕:启动流程的详细分解
📚 故事场景:启动魔法引擎的步骤
老巫师指着魔法引擎上的流程图说:"看,启动过程就像魔法引擎的启动步骤,一步接一步。"

严谨推理:
SpringBoot的启动流程可以分解为以下几个关键步骤:
魔法实践(详细步骤):
java
public class SpringApplication {
// 核心运行方法 - 魔法引擎的启动流程
public ConfigurableApplicationContext run(String... args) {
// 步骤1:启动计时器 - 记录启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置Headless模式(无头模式,用于服务器环境)
configureHeadlessProperty();
// 步骤2:获取运行监听器并启动
// 这些监听器会监听启动过程中的各个事件
SpringApplicationRunListeners listeners = getRunListeners(args);
// 步骤3:发布应用启动事件
// ApplicationStartingEvent - 应用开始启动
listeners.starting();
try {
// 步骤4:准备应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 步骤5:准备环境
// 加载application.properties/yml,处理Profile等
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 步骤6:打印Banner(就是启动时那个Spring logo)
Banner printedBanner = printBanner(environment);
// 步骤7:创建应用上下文
// 根据webApplicationType创建不同的ApplicationContext
context = createApplicationContext();
// 步骤8:准备异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class },
context
);
// 步骤9:准备上下文
// 设置环境、注册单例Bean等
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 步骤10:刷新上下文 - 这是最核心的步骤!
refreshContext(context);
// 步骤11:刷新后的后置处理
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
// 步骤12:发布应用启动完成事件
// ApplicationStartedEvent - 应用启动完成
listeners.started(context);
// 步骤13:执行Runner(CommandLineRunner和ApplicationRunner)
callRunners(context, applicationArguments);
// 步骤14:发布应用就绪事件
// ApplicationReadyEvent - 应用准备就绪,可以接收请求
listeners.running(context);
} catch (Throwable ex) {
// 处理启动失败的情况
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
return context;
}
}
🎭 第三幕:刷新上下文------Spring容器的核心
📚 故事场景:魔法容器的刷新
老巫师打开魔法容器的内部结构图:"刷新上下文是启动过程中最复杂的一步,它完成了Spring容器的初始化。"

严谨推理:
refreshContext()
方法调用了AbstractApplicationContext.refresh()
,这是Spring容器初始化的核心方法。
魔法实践(源码分析):
java
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
// Spring容器的核心刷新方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第一步:准备刷新
// 设置启动时间、激活状态、初始化属性源等
prepareRefresh();
// 第二步:获取新的BeanFactory
// 销毁之前的BeanFactory(如果有),创建新的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 第三步:准备BeanFactory以供使用
// 配置BeanFactory的标准上下文特征,如类加载器、后置处理器等
prepareBeanFactory(beanFactory);
try {
// 第四步:允许子类后置处理BeanFactory
// 在BeanFactory标准初始化之后,但在Bean定义加载之前
postProcessBeanFactory(beanFactory);
// 第五步:调用BeanFactory后置处理器
// 这是自动配置发生的关键步骤!
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册Bean后置处理器
// 这些处理器会在Bean创建过程中进行拦截
registerBeanPostProcessors(beanFactory);
// 第七步:初始化消息源(用于国际化)
initMessageSource();
// 第八步:初始化事件广播器
initApplicationEventMulticaster();
// 第九步:子类初始化其他特殊的Bean
// 在SpringBoot中,这里会创建内嵌的Web服务器!
onRefresh();
// 第十步:注册监听器
registerListeners();
// 第十一步:完成BeanFactory的初始化
// 实例化所有剩余的非懒加载单例Bean
finishBeanFactoryInitialization(beanFactory);
// 第十二步:完成刷新过程
// 发布ContextRefreshedEvent事件
finishRefresh();
} catch (BeansException ex) {
// 处理异常...
destroyBeans();
cancelRefresh(ex);
throw ex;
} finally {
resetCommonCaches();
}
}
}
// 调用BeanFactory后置处理器 - 自动配置发生在这里!
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// 获取所有BeanFactoryPostProcessor
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// 这里会调用ConfigurationClassPostProcessor
// 它会解析@Configuration注解的类,包括我们的@SpringBootApplication主类
// 进而触发@EnableAutoConfiguration,加载自动配置类
}
// 完成BeanFactory初始化 - 实例化所有单例Bean
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化ConversionService(用于类型转换)
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// 注册默认的字符串解析器
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// 初始化LoadTimeWeaverAware Bean(AOP相关)
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// 停止使用临时类加载器
beanFactory.setTempClassLoader(null);
// 允许缓存所有Bean定义元数据
beanFactory.freezeConfiguration();
// 实例化所有剩余的非懒加载单例Bean!
beanFactory.preInstantiateSingletons();
}
}
🔍 第四幕:自动配置在启动过程中的位置
📚 故事场景:自动配置的触发时机
小白问:"自动配置是在哪个步骤发生的呢?"
老巫师指着流程图说:"自动配置发生在invokeBeanFactoryPostProcessors
这一步。"

严谨推理:
自动配置是通过ConfigurationClassPostProcessor
这个BeanFactory后置处理器完成的。它负责解析@Configuration
注解的类,并加载自动配置类。
魔法实践:
java
// ConfigurationClassPostProcessor - 配置类后置处理器
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 处理配置类 - 这是自动配置的入口!
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 步骤1:收集所有配置类候选Bean
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 检查是否是配置类(有@Configuration注解)
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 步骤2:解析配置类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解析配置类,包括:
// - @ComponentScan:扫描指定包下的@Component组件
// - @Import:导入其他配置类,包括@EnableAutoConfiguration
// - @Bean:处理@Bean方法
parser.parse(candidates);
parser.validate();
// 获取解析得到的配置类
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// 步骤3:读取配置类中的Bean定义
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// 检查是否有新注册的Bean定义需要解析(递归处理)
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.isFullConfigurationClass(bd) ||
ConfigurationClassUtils.isLiteConfigurationClass(bd) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
} while (!candidates.isEmpty());
}
}
自动配置的触发流程:
-
解析主配置类 :
@SpringBootApplication
标注的类被解析 -
处理@EnableAutoConfiguration :通过
@Import(AutoConfigurationImportSelector.class)
导入自动配置 -
加载自动配置类 :从
spring.factories
中加载自动配置类 -
条件注解过滤:根据条件注决定是否启用自动配置类
-
注册Bean定义:将自动配置类中定义的Bean注册到容器
🧪 第五幕:启动流程中的扩展点
📚 故事场景:魔法引擎的扩展接口
老巫师说:"SpringBoot提供了很多扩展点,允许我们在启动过程中插入自定义逻辑。"

魔法实践:使用扩展点
1. ApplicationContextInitializer
java
// 自定义初始化器 - 在应用上下文刷新前执行
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("=== 执行自定义ApplicationContextInitializer ===");
// 可以在这里进行一些自定义的上下文配置
// 比如设置环境变量、注册自定义的Bean等
// 示例:添加一个自定义的环境属性
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.getPropertySources().addFirst(new MyPropertySource());
}
}
// 注册初始化器:在META-INF/spring.factories中配置
// org.springframework.context.ApplicationContextInitializer=com.xiaobai.MyApplicationContextInitializer
2. ApplicationListener
java
// 自定义监听器,监听启动事件
public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> {
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
// 应用开始启动
if (event instanceof ApplicationStartingEvent) {
System.out.println("=== 应用开始启动 ===");
}
// 环境准备完成
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
System.out.println("=== 环境准备完成 ===");
}
// 应用上下文准备完成
else if (event instanceof ApplicationPreparedEvent) {
System.out.println("=== 应用上下文准备完成 ===");
}
// 应用启动完成
else if (event instanceof ApplicationStartedEvent) {
System.out.println("=== 应用启动完成 ===");
}
// 应用就绪,可以接收请求
else if (event instanceof ApplicationReadyEvent) {
System.out.println("=== 应用准备就绪 ===");
}
}
}
3. CommandLineRunner和ApplicationRunner
java
// CommandLineRunner:在应用启动后执行,接收原始命令行参数
@Component
@Order(1) // 可以指定执行顺序
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("=== 执行CommandLineRunner ===");
System.out.println("命令行参数: " + Arrays.toString(args));
// 可以在这里执行一些启动后的初始化任务
// 比如:初始化缓存、加载基础数据、启动定时任务等
}
}
// ApplicationRunner:类似CommandLineRunner,但参数被封装得更好
@Component
@Order(2)
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("=== 执行ApplicationRunner ===");
System.out.println("非选项参数: " + args.getNonOptionArgs());
System.out.println("选项参数名称: " + args.getOptionNames());
// 同样可以执行启动后的任务
// 参数处理比CommandLineRunner更友好
}
}
4. BeanPostProcessor
java
// Bean后置处理器 - 在Bean初始化前后执行
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在Bean初始化之前调用
if (bean instanceof MyService) {
System.out.println("=== 初始化MyService之前: " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在Bean初始化之后调用
if (bean instanceof MyService) {
System.out.println("=== 初始化MyService之后: " + beanName);
}
return bean;
}
}
🌟 小白的顿悟时刻
经过这番深入学习,小白终于明白了SpringBoot启动流程的奥秘:

"我明白了!"小白兴奋地说,"SpringBoot的启动流程就像魔法引擎的启动:"
-
SpringApplication初始化:组装引擎部件
-
准备环境:准备魔法燃料和环境
-
创建应用上下文:构建魔法容器
-
刷新上下文:启动魔法容器,完成Bean的创建和依赖注入
-
自动配置:根据条件自动装配魔法组件
-
执行Runner:启动后的收尾工作
老巫师满意地点头:"现在你已经深入理解了SpringBoot的启动原理,这将帮助你在遇到启动问题时能够快速定位,并且能够更好地扩展SpringBoot的功能。"
🎯 启动流程的核心原理总结
🔮 启动流程的关键步骤:
-
初始化SpringApplication:推断应用类型、加载初始化器和监听器
-
运行run方法:启动计时器、准备环境、创建上下文、刷新上下文
-
刷新上下文:执行BeanFactory后置处理器(包括自动配置)、注册Bean后置处理器、初始化消息源和事件广播器、实例化单例Bean
-
后置处理:执行Runner、发布启动完成事件
🧩 自动配置的触发时机:
自动配置发生在刷新上下文的invokeBeanFactoryPostProcessors
阶段,由ConfigurationClassPostProcessor
解析配置类,进而触发AutoConfigurationImportSelector
加载自动配置类。
🚀 最佳实践建议:
-
理解启动顺序:知道各个扩展点的执行时机
-
善用扩展点:使用Initializer、Listener、Runner进行自定义扩展
-
掌握调试技巧:通过调试启动过程理解原理
-
优化启动速度:减少不必要的自动配置类、使用懒加载等
🔧 常见问题排查:
问题现象 | 可能原因 | 解决方案 |
---|---|---|
启动很慢 | 自动配置类太多 | 使用debug=true 查看报告,排除不必要的自动配置 |
Bean创建失败 | 依赖注入问题 | 检查Bean的依赖关系,使用@Lazy 解决循环依赖 |
配置文件不生效 | 加载顺序问题 | 检查配置文件的加载顺序和Profile设置 |
监听器不执行 | 注册方式错误 | 检查是否在spring.factories 中正确注册 |
🎉 恭喜!小白已经深入理解了SpringBoot启动流程的魔法原理!
现在,小白不仅能够使用SpringBoot,还理解了它的内部工作原理。这让他能够在未来的开发中更加得心应手,无论是解决问题还是进行扩展,都有了坚实的基础。
"记住,小白,"老巫师最后说,"理解原理是为了更好地使用工具。现在,你已经具备了成为SpringBoot魔法大师的内功,去创造更强大的魔法吧!"