当前位置: 首页 > news >正文

SpringBoot的启动流程原理——小白的魔法引擎探秘

小白已经掌握了自动配置的魔法,但他对SpringBoot应用的启动过程仍然充满好奇:当我们运行main方法时,背后到底发生了哪些事情?今天,我们将揭开SpringBoot启动流程的神秘面纱。

📖 故事序幕:启动魔法引擎

在魔法学院的引擎室内,老巫师指着一台巨大的魔法引擎对小白说:"这就是SpringBoot的启动引擎,它负责启动整个魔法世界。让我们一步步拆解它的工作原理。"


🧙 第一幕:启动的起点——SpringApplication初始化

📚 故事场景:魔法引擎的组装

老巫师开始讲解:"首先,我们需要组装魔法引擎的各个部件。"

// 小白的启动类
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {// 这就是启动魔法的咒语!SpringApplication.run(DemoApplication.class, args);}
}

严谨推理:
当调用SpringApplication.run(DemoApplication.class, args)时,SpringBoot启动流程开始。首先会创建一个SpringApplication实例,并进行初始化。

魔法实践(源码分析):

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的启动流程可以分解为以下几个关键步骤:

魔法实践(详细步骤):

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创建不同的ApplicationContextcontext = 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容器初始化的核心方法。

魔法实践(源码分析):

public abstract class AbstractApplicationContext extends DefaultResourceLoaderimplements ConfigurableApplicationContext {// Spring容器的核心刷新方法@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 第一步:准备刷新// 设置启动时间、激活状态、初始化属性源等prepareRefresh();// 第二步:获取新的BeanFactory// 销毁之前的BeanFactory(如果有),创建新的BeanFactoryConfigurableListableBeanFactory 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的初始化// 实例化所有剩余的非懒加载单例BeanfinishBeanFactoryInitialization(beanFactory);// 第十二步:完成刷新过程// 发布ContextRefreshedEvent事件finishRefresh();} catch (BeansException ex) {// 处理异常...destroyBeans();cancelRefresh(ex);throw ex;} finally {resetCommonCaches();}}}// 调用BeanFactory后置处理器 - 自动配置发生在这里!protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {// 获取所有BeanFactoryPostProcessorPostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// 这里会调用ConfigurationClassPostProcessor// 它会解析@Configuration注解的类,包括我们的@SpringBootApplication主类// 进而触发@EnableAutoConfiguration,加载自动配置类}// 完成BeanFactory初始化 - 实例化所有单例Beanprotected 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注解的类,并加载自动配置类。

魔法实践:

// ConfigurationClassPostProcessor - 配置类后置处理器
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {// 处理配置类 - 这是自动配置的入口!processConfigBeanDefinitions(registry);}public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 步骤1:收集所有配置类候选BeanList<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());}
}

自动配置的触发流程:

  1. 解析主配置类@SpringBootApplication标注的类被解析

  2. 处理@EnableAutoConfiguration:通过@Import(AutoConfigurationImportSelector.class)导入自动配置

  3. 加载自动配置类:从spring.factories中加载自动配置类

  4. 条件注解过滤:根据条件注决定是否启用自动配置类

  5. 注册Bean定义:将自动配置类中定义的Bean注册到容器


🧪 第五幕:启动流程中的扩展点

📚 故事场景:魔法引擎的扩展接口

老巫师说:"SpringBoot提供了很多扩展点,允许我们在启动过程中插入自定义逻辑。"

魔法实践:使用扩展点

1. ApplicationContextInitializer

// 自定义初始化器 - 在应用上下文刷新前执行
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic 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

// 自定义监听器,监听启动事件
public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> {@Overridepublic 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

// CommandLineRunner:在应用启动后执行,接收原始命令行参数
@Component
@Order(1)  // 可以指定执行顺序
public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic 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 {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("=== 执行ApplicationRunner ===");System.out.println("非选项参数: " + args.getNonOptionArgs());System.out.println("选项参数名称: " + args.getOptionNames());// 同样可以执行启动后的任务// 参数处理比CommandLineRunner更友好}
}

4. BeanPostProcessor

// Bean后置处理器 - 在Bean初始化前后执行
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 在Bean初始化之前调用if (bean instanceof MyService) {System.out.println("=== 初始化MyService之前: " + beanName);}return bean;}@Overridepublic 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的功能。"


🎯 启动流程的核心原理总结

🔮 启动流程的关键步骤:

  1. 初始化SpringApplication:推断应用类型、加载初始化器和监听器

  2. 运行run方法:启动计时器、准备环境、创建上下文、刷新上下文

  3. 刷新上下文:执行BeanFactory后置处理器(包括自动配置)、注册Bean后置处理器、初始化消息源和事件广播器、实例化单例Bean

  4. 后置处理:执行Runner、发布启动完成事件

🧩 自动配置的触发时机:

自动配置发生在刷新上下文的invokeBeanFactoryPostProcessors阶段,由ConfigurationClassPostProcessor解析配置类,进而触发AutoConfigurationImportSelector加载自动配置类。

🚀 最佳实践建议:

  1. 理解启动顺序:知道各个扩展点的执行时机

  2. 善用扩展点:使用Initializer、Listener、Runner进行自定义扩展

  3. 掌握调试技巧:通过调试启动过程理解原理

  4. 优化启动速度:减少不必要的自动配置类、使用懒加载等

🔧 常见问题排查:

问题现象可能原因解决方案
启动很慢自动配置类太多使用debug=true查看报告,排除不必要的自动配置
Bean创建失败依赖注入问题检查Bean的依赖关系,使用@Lazy解决循环依赖
配置文件不生效加载顺序问题检查配置文件的加载顺序和Profile设置
监听器不执行注册方式错误检查是否在spring.factories中正确注册

🎉 恭喜!小白已经深入理解了SpringBoot启动流程的魔法原理!

现在,小白不仅能够使用SpringBoot,还理解了它的内部工作原理。这让他能够在未来的开发中更加得心应手,无论是解决问题还是进行扩展,都有了坚实的基础。

"记住,小白,"老巫师最后说,"理解原理是为了更好地使用工具。现在,你已经具备了成为SpringBoot魔法大师的内功,去创造更强大的魔法吧!"

http://www.dtcms.com/a/507016.html

相关文章:

  • Vue3 + Element Plus 弹框树形结构首次打开不更新问题排查与解决
  • 我先做个网站怎么做网络推广技术外包
  • 互联网公司排名前十名名单seo整站优化更能准确获得客户
  • 网络运维学习笔记
  • Helm、HPA 与 Rancher:Kubernetes(十) 生态核心工具详解
  • Docker常见问题
  • 拟合优度:模型与数据的契合之度
  • 理解 Python 的有序字典 OrderedDict
  • 狙击生态的演化史:从抢跑到模型套利
  • 用C语言实现外观模式
  • Git三路合并算法的弊端
  • 网站模板文件扫描工作招聘58同城
  • 网站建设上线问题企业网站的推广方法有哪些
  • LeetCode:207. 课程表
  • Oracle EBS ERP开发——报表生成Excel标准模板设计
  • CANoe基础讲解02:掌握CANoe Trace窗口
  • Kanass V1.3.3版本发布,支持在线安装与消息配置
  • 第十七篇:本地模型部署:使用DeepSeek开源模型进行离线推理
  • 【STM32项目开源】基于STM32的智能家居环境监测系统
  • 串口AT指令控制EC20连接MQTT服务器
  • 如何实现企业网站推广的系统性网站建设腾讯课堂
  • 全网营销型的网站wordpress 开源吗
  • 常见Linux环境变量深度解析
  • Jetson上安装TensorRT
  • 开发避坑指南(62):解决URLDecoder:Illegal hex characters in escape (%) pattern 异常
  • DAX分列年月日
  • 【开题答辩实录分享】以《自然灾害隐患点管理信息系统》为例进行答辩实录分享
  • dpdk如何与内核进行交互——vdev的用法
  • 丝杆模组如何满足高精度加工设备的高要求?
  • 咋做网站泉州网络推广专员