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

小白已经掌握了自动配置的魔法,但他对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());
    }
}

自动配置的触发流程:

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

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

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

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

  5. 注册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的功能。"


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

🔮 启动流程的关键步骤:

  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魔法大师的内功,去创造更强大的魔法吧!"

相关推荐
白露与泡影4 小时前
BAT 大厂 java高频面试题汇总:JVM+Spring+ 分布式 +tomcat+MyBatis
java·jvm·spring
Han.miracle4 小时前
数据结构——排序的学习(一)
java·数据结构·学习·算法·排序算法
摇滚侠4 小时前
Spring Boot 3零基础教程,WEB 开发 通过配置类代码方式修改静态资源配置 笔记32
java·spring boot·笔记
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ5 小时前
mapper.xml sql动态表查询配置
xml·java·sql
YuanlongWang5 小时前
C# 基础——多态的实现方式
java·c#
夜幽青玄5 小时前
mybatis-plus调用报 org.springframework.dao.DataIntegrityViolationException 错误处理
开发语言·python·mybatis
洲覆5 小时前
Redis 内存淘汰策略
开发语言·数据库·redis·缓存
偶尔贪玩的骑士5 小时前
Kioptrix Level 1渗透测试
linux·开发语言·网络安全·php
小咕聊编程5 小时前
【含文档+PPT+源码】基于spring boot的固定资产管理系统
java·spring boot·后端