【SpringBoot】38 核心功能 - 高级特性- Spring Boot 中的应用启动过程详解

前言

SpringBoot应用的启动过程是一个复杂但设计精巧的流程,了解这个过程对于深入理解SpringBoot工作原理至关重要。本文将详细解析SpringBoot应用的完整[启动流程]。

一、SpringApplication初始化阶段

1. 创建SpringApplication实例

SpringBoot应用启动的第一步是创建SpringApplication实例:

typescript 复制代码
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

AI写代码java
运行
123456

SpringApplication的构造函数中,会进行以下关键操作:

scss 复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 保存主配置类信息
    this.resourceLoader = resourceLoader;
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 判断应用类型:SERVLET、REACTIVE 或 NONE
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 获取并初始化BootstrapRegistryInitializer
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    
    // 获取ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    
    // 获取ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 推断主应用类
    this.mainApplicationClass = deduceMainApplicationClass();
}

AI写代码java
运行
12345678910111213141516171819202122

2. 加载SPI扩展组件

SpringBoot通过spring.factories机制加载各种扩展组件:

  • BootstrapRegistryInitializer: 引导注册表初始化器
  • ApplicationContextInitializer: 应用上下文初始化器
  • ApplicationListener: 应用事件监听器

二、应用运行阶段

1. 启动计时与引导上下文

ini 复制代码
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    
    // 创建引导上下文
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    
    // 获取运行监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
}

AI写代码java
运行
1234567891011121314

2. 环境准备

环境准备阶段负责配置应用运行所需的[环境变量]和配置属性:

scss 复制代码
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    
    // 创建适当类型的环境对象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    
    // 配置环境:加载配置文件、命令行参数等
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    
    // 发布EnvironmentPrepared事件
    listeners.environmentPrepared(bootstrapContext, environment);
    
    // 将环境绑定到SpringApplication
    bindToSpringApplication(environment);
    
    return environment;
}

AI写代码java
运行
123456789101112131415161718

环境配置过程

  • 返回或者创建基础设施信息对象:StandardServletEnvironment
  • 配置环境信息对象
  • 读取所有的配置源的配置属性值
  • 绑定环境信息
  • 监听器调用 listener.environmentPrepared(),通知所有监听器当前环境准备完成

3. 创建IOC容器

根据应用类型创建相应的ApplicationContext:

kotlin 复制代码
protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
}

AI写代码java
运行
123

根据项目类型创建容器

  • 对于Servlet应用,会创建 AnnotationConfigServletWebServerApplicationContext
  • 对于Reactive应用,会创建 AnnotationConfigReactiveWebServerApplicationContext
  • 对于普通应用,会创建 AnnotationConfigApplicationContext

4. 准备IOC容器

scss 复制代码
private void prepareContext(DefaultBootstrapContext bootstrapContext, 
                           ConfigurableApplicationContext context,
                           ConfigurableEnvironment environment, 
                           SpringApplicationRunListeners listeners,
                           ApplicationArguments applicationArguments, 
                           Banner printedBanner) {
    
    // 设置环境
    context.setEnvironment(environment);
    
    // 应用上下文后置处理
    postProcessApplicationContext(context);
    
    // 应用初始化器
    applyInitializers(context);
    
    // 发布ApplicationContextInitialized事件
    listeners.contextPrepared(context);
    
    // 注册Bean定义
    load(context, sources.toArray(new Object[0]));
    
    // 发布ApplicationContextLoaded事件
    listeners.contextLoaded(context);
}

AI写代码java
运行
12345678910111213141516171819202122232425

准备ApplicationContext IOC容器的基本信息 prepareContext()

  • 保存环境信息
  • IOC容器的后置处理流程
  • 应用初始化器:applyInitializers
  • 遍历所有的 ApplicationContextInitializer,调用 initialize,来对IOC容器进行初始化扩展功能
  • 遍历所有的监听器调用 contextPrepared,通知所有的监听器contextPrepare
  • 所有的监听器调用 contextLoaded,通知所有的监听器contextLoaded

5. 刷新IOC容器

这是SpringBoot启动过程中最核心的步骤:

scss 复制代码
private void refreshContext(ConfigurableApplicationContext context) {
    // 注册关闭钩子
    if (this.registerShutdownHook) {
        shutdownHook.registerApplicationContext(context);
    }
    
    // 执行刷新操作
    refresh(context);
}

AI写代码java
运行
123456789

实际的刷新操作委托给Spring Framework的refresh()方法:

typescript 复制代码
protected void refresh(ApplicationContext applicationContext) {
    applicationContext.refresh();
}

AI写代码java
运行
123

refresh()方法中会完成:

  • BeanFactory的创建和配置
  • Bean的后置处理器注册
  • 消息源、事件广播器等基础组件的初始化
  • Web服务器的创建和启动
  • 单例Bean的实例化

刷新IOC容器,refreshContext

  • 创建容器中的所有组件(Spring注解)
  • 启动内嵌的Web服务器(如Tomcat)
  • 完成所有Bean的创建和依赖注入

6. 容器刷新后处理

typescript 复制代码
protected void afterRefresh(ConfigurableApplicationContext context,
                           ApplicationArguments args) {
    // 调用ApplicationRunner和CommandLineRunner
}

AI写代码java
运行
1234

7. 调用Runners

scss 复制代码
private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    
    // 获取容器中的ApplicationRunner
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    
    // 获取容器中的CommandLineRunner  
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    
    // 合并所有runner并且按照@Order进行排序
    AnnotationAwareOrderComparator.sort(runners);
    
    // 遍历所有的runner,调用run方法
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

AI写代码java
运行
12345678910111213141516171819202122

调用所有runners

  • 获取容器中的 ApplicationRunner
  • 获取容器中的 CommandLineRunner
  • 合并所有runner并且按照Order进行排序
  • 遍历所有的runner,调用run方法

8. 启动完成事件发布

lua 复制代码
// 所有监听器调用listeners.started(context),通知所有的监听器started
listeners.started(context);

// 调用所有监听器的running方法listeners.running(context),通知所有的监听器running  
listeners.running(context);

AI写代码java
运行
12345

9. 异常处理

在整个启动过程中,如果出现异常,会进行相应的处理:

php 复制代码
try {
    // 启动流程...
} catch (Throwable ex) {
    // 调用Listener的failed方法,通知所有的监听器启动失败
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
}

AI写代码java
运行
1234567

异常处理机制

  • 如果以上有异常,调用Listener的failed
  • 调用所有监听器的running方法,如果running有问题,继续通知failed

三、关键组件详解

1. 事件监听机制

SpringBoot的启动过程通过事件监听机制实现了高度的扩展性:

  • ApplicationStartingEvent: 应用启动开始时发布
  • ApplicationEnvironmentPreparedEvent: 环境准备完成后发布
  • ApplicationContextInitializedEvent: 应用上下文初始化后发布
  • ApplicationPreparedEvent: Bean定义加载完成后发布
  • ApplicationStartedEvent: 应用启动完成后发布
  • ApplicationReadyEvent: 应用准备就绪后发布
  • ApplicationFailedEvent: 启动失败时发布

2. Runner接口

ApplicationRunnerCommandLineRunner提供了在应用启动后执行特定代码的机制:

java 复制代码
@Component
@Order(1)
public class MyApplicationRunner implements ApplicationRunner {
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 应用启动后执行的逻辑
        System.out.println("ApplicationRunner执行完成");
    }
}

@Component  
@Order(2)
public class MyCommandLineRunner implements CommandLineRunner {
    
    @Override
    public void run(String... args) throws Exception {
        // 命令行参数处理逻辑
        System.out.println("CommandLineRunner执行完成");
    }
}

AI写代码java
运行
123456789101112131415161718192021

总结

SpringBoot的启动过程是一个精心设计的流程,通过模块化的设计和事件驱动机制,实现了高度的可扩展性和灵活性。理解这个启动过程对于深入掌握SpringBoot框架、进行自定义扩展和故障排查都具有重要意义。

整个启动过程从创建SpringApplication实例开始,经过环境准备、容器创建、Bean加载、后置处理等多个阶段,最终完成应用的启动并对外提供服务。每个阶段都提供了相应的扩展点,允许开发者在适当的时机介入并添加自定义逻辑。

相关推荐
bcbnb4 小时前
如何解析iOS崩溃日志:从获取到符号化分析
后端
许泽宇的技术分享4 小时前
当AI学会“说人话“:Azure语音合成技术的魔法世界
后端·python·flask
用户69371750013844 小时前
4.Kotlin 流程控制:强大的 when 表达式:取代 Switch
android·后端·kotlin
用户69371750013844 小时前
5.Kotlin 流程控制:循环的艺术:for 循环与区间 (Range)
android·后端·kotlin
vx_bisheyuange4 小时前
基于SpringBoot的宠物商城网站的设计与实现
spring boot·后端·宠物
bcbnb4 小时前
全面解析网络抓包工具使用:Wireshark和TCPDUMP教程
后端
leonardee4 小时前
Spring Security安全框架原理与实战
java·后端
回家路上绕了弯5 小时前
包冲突排查指南:从发现到解决的全流程实战
分布式·后端
爱分享的鱼鱼5 小时前
部署Vue+Java Web应用到云服务器完整指南
前端·后端·全栈
麦麦麦造5 小时前
比 pip 快 100 倍!更现代的 python 包管理工具,替代 pip、venv、poetry!
后端·python