02-Spring 容器的初始化与 Spring Boot 启动过程解析

4 阅读7分钟

Spring 容器的初始化与 Spring Boot 启动过程解析

📌 前言

续接上文,本章继续。Spring 是企业级开发的核心框架之一,而 Spring Boot 进一步简化了 Spring 应用的开发和部署。了解 Spring 容器的初始化过程以及 Spring Boot 的启动流程,不仅有助于排查异常,还能帮助我们更好地理解 Spring 的核心机制。

本文将通过源码视角,深入解析:

  • Spring 容器的初始化阶段
  • Spring Boot 的启动流程
  • 各个关键类和方法的作用

🌱 一、Spring 容器的初始化过程

1. 容器的基本概念

Spring 容器是一个管理 Bean 的工厂,它负责 Bean 的创建、依赖注入、生命周期管理和销毁。Spring 容器主要有以下两种:

  • BeanFactory:提供基础的依赖注入功能,通常用于资源受限的场景。
  • ApplicationContext:基于 BeanFactory 扩展,提供更强大的功能,如事件发布、国际化支持等。

💡 Q: 为什么在实际开发中通常推荐使用 ApplicationContext 而不是 BeanFactory?
A: 因为 ApplicationContext 提供了更多的功能,例如事件监听、国际化支持、AOP、事务管理等,同时启动速度也更快。


2. 容器初始化的核心流程

Spring 中,容器的初始化主要由 refresh() 方法驱动。它位于 AbstractApplicationContext 中。

源码路径org.springframework.context.support.AbstractApplicationContext

@Override
public void refresh() throws BeansException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 初始化环境配置
        prepareRefresh();
        
        // 2. 创建 BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3. BeanFactory 配置
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 执行 BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);

            // 5. 注册 BeanPostProcessor
            registerBeanPostProcessors(beanFactory);

            // 6. 初始化消息源
            initMessageSource();

            // 7. 初始化事件广播器
            initApplicationEventMulticaster();

            // 8. 执行子类的 onRefresh 方法
            onRefresh();

            // 9. 初始化所有非懒加载的单例 Bean
            finishBeanFactoryInitialization(beanFactory);

            // 10. 发布容器启动事件
            finishRefresh();
        } catch (BeansException ex) {
            throw ex;
        }
    }
}

💡 Q: 你能简单讲解一下 refresh() 方法中的核心步骤吗?
A:
refresh() 主要完成环境准备、Bean 加载、依赖注入、事件发布等任务。关键步骤包括:

  • prepareRefresh():初始化配置,加载环境变量。
  • obtainFreshBeanFactory():创建 BeanFactory 并加载 BeanDefinition。
  • invokeBeanFactoryPostProcessors():执行 BeanFactoryPostProcessor,如配置类的解析。
  • finishBeanFactoryInitialization():实例化所有非懒加载的单例 Bean。
  • finishRefresh():发布 ContextRefreshedEvent 通知监听器。

🔎 二、Spring 中的三级缓存机制

Spring 为了解决 Setter 注入 场景下的循环依赖,采用了 三级缓存机制

  • 一级缓存(singletonObjects):存储已初始化完成的单例对象。
  • 二级缓存(earlySingletonObjects):存储实例化但未初始化的对象。
  • 三级缓存(singletonFactories):存储生成早期引用的 ObjectFactory,用于解决循环依赖。

源码示例

singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

当 A 依赖 B,B 又依赖 A 时,Spring 会通过 ObjectFactory 提前暴露 A 的引用,避免循环依赖导致的 BeanCurrentlyInCreationException

💡 Q: 为什么 Spring 需要三级缓存而不是直接用二级缓存解决循环依赖?
A:
三级缓存相比二级缓存更加灵活。在对象未完成初始化前,Spring 可以通过 ObjectFactory 提供一个代理对象,避免暴露不完整的 Bean,从而增强安全性。
具体也可查看上一篇进行详细了解。


🚀 三、Spring Boot 启动过程解析

Spring Boot 的启动入口通常是 SpringApplication.run()

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return new SpringApplication(primarySource).run(args);
}

Spring Boot 启动的核心流程

  1. 启动监听器加载

    • 加载 SpringApplicationRunListeners,监听启动事件。
  2. 环境准备

    • 创建并配置 ConfigurableEnvironment,加载外部配置文件。
  3. 创建容器

    • 根据项目类型选择 AnnotationConfigServletWebServerApplicationContext 或其他合适的 ApplicationContext。
  4. 刷新容器

    • 调用 refresh(),完成 Bean 的实例化与初始化。
  5. 执行 Runner

    • 执行所有的 CommandLineRunnerApplicationRunner

💡 Q: Spring Boot 的自动配置原理是什么?
A:
Spring Boot 使用 @EnableAutoConfiguration 注解,通过 SpringFactoriesLoader 加载 spring.factories 文件中的自动配置类。这些类会在启动时生效,实现自动配置。


📚 总结

  • Spring 容器通过 refresh() 完成环境配置、Bean 加载、依赖注入和事件发布。
  • Spring 使用三级缓存解决 Setter 注入的循环依赖问题。
  • Spring Boot 的启动过程包括环境准备、容器创建、自动配置等阶段。

💬 讨论与思考

  • Spring 容器的事件机制是如何工作的?
  • 如果要优化 Spring Boot 的启动速度,你会怎么做?
  • 你在实际开发中遇到过哪些与 Spring 启动相关的问题?

🌱 Spring 容器的事件机制是如何工作的?

Spring 的事件机制基于 观察者模式,主要由以下三部分组成:

  • 事件(ApplicationEvent):表示 Spring 容器中的特定事件。
  • 事件发布者(ApplicationEventPublisher):用于发布事件。
  • 事件监听器(ApplicationListener):用于监听和处理事件。

📌 事件的基本流程:

  1. 事件定义:继承 ApplicationEvent,创建自定义事件类。
  2. 事件监听:实现 ApplicationListener 接口或使用 @EventListener 注解监听事件。
  3. 事件发布:通过 ApplicationEventPublisher 发布事件。
📦 源码解析
  • ApplicationContext 本身实现了 ApplicationEventPublisher 接口,可以直接发布事件。
  • 事件会通过 SimpleApplicationEventMulticaster 进行分发,通知所有监听器。
  • 异步事件监听则通过 @Async 注解实现。
applicationContext.publishEvent(new MyCustomEvent(this));

💡 Q: Spring 的事件机制的优势是什么?
A: Spring 事件机制解耦了事件的生产者和消费者,使得组件之间的通信更加灵活,尤其适合应用于事务提交后、资源释放等场景。


🚀 如何优化 Spring Boot 的启动速度?

优化 Spring Boot 的启动速度可以从以下几个方面入手:

1. 减少不必要的 Bean 加载

  • 使用 @ConditionalOnMissingBean@ConditionalOnProperty 等条件注解,按需加载 Bean。
  • 合理配置 spring.main.lazy-initialization=true 进行懒加载。

2. 禁用不必要的自动配置

  • 使用 spring.autoconfigure.exclude 排除不需要的自动配置类。
  • 使用 META-INF/spring.factories 定义精简的自动配置类。

3. 使用 AOT 编译

  • 利用 Spring Boot 3 的 Ahead-of-Time (AOT) 编译,减少启动时的类加载和反射操作。

4. 优化数据源和缓存

  • 使用 HikariCP 等高性能数据库连接池。
  • 使用 Caffeine 或 Redis 缓存,减少数据库查询压力。

5. 并行化 Bean 初始化

  • 在合适的场景下通过异步任务加快 Bean 初始化。

💡 Q: Spring Boot 使用 AOT 编译的主要作用是什么?
A: AOT 编译可以在构建阶段生成静态代码,避免运行时的反射和字节码增强,从而加快启动速度。


🔎 常见的 Spring 启动问题及解决方案

在实际开发中,常见的 Spring 启动相关问题包括:

🔎 1. Bean 循环依赖问题

场景

  • 使用 @Autowired 进行字段注入时容易出现循环依赖问题。

解决方法

  • 使用构造器注入代替字段注入。
  • 如果无法避免,可开启 allowCircularReferences
spring:
  main:
    allow-circular-references: true

🔎 2. 配置加载异常

场景

  • 配置文件缺失或配置项拼写错误。

解决方法

  • 使用 @ConfigurationProperties 进行类型安全的配置管理。
  • 使用 Spring Boot 的 spring-boot-configuration-processor 生成自动提示。

🔎 3. 端口占用或启动失败

场景

  • 启动时提示端口已被占用。

解决方法

  • 临时更换端口:
server:
  port: 0 # 使用随机端口
  • 使用 lsof -i :8080netstat 查看占用端口的进程并关闭。

🔎 4. 依赖冲突问题

场景

  • 启动时报 ClassNotFoundExceptionNoSuchMethodError

解决方法

  • 使用 mvn dependency:tree 分析依赖冲突。
  • 使用 spring-boot-dependencies 中推荐的版本。

🔎 5. 数据源初始化失败

场景

  • 配置数据源时,数据库连接异常或驱动未加载。

解决方法

  • 检查数据库 URL、用户名、密码等配置项。
  • 使用 spring.datasource.hikari 优化连接池配置。

📚 总结

  • Spring 的事件机制基于观察者模式,通过事件发布和监听解耦组件之间的依赖。
  • Spring Boot 的启动速度优化可以从减少 Bean 加载、禁用不必要的自动配置、AOT 编译等方面入手。
  • 针对启动过程中的问题,如循环依赖、配置异常、端口占用等,可以根据具体场景采取有效的解决方案。

通过以上,应该可以很快了解初始化跟启动的流程,也能更快的去排查跟定位问题,已经可以调用Spring先关的API进行相关业务逻辑的编写。 如果你还有其他问题或想了解更多,欢迎留言交流! 😊