" />
文章

Spring Boot 加载与 Bean 加载过程

Spring Boot 加载与 Bean 加载过程

1. 引言

Spring Boot 的核心在于约定大于配置,它通过自动配置让开发者可以快速、简便地搭建项目。在底层,Spring Boot 仍然依赖于 Spring Framework 的 ApplicationContext(常见如 AnnotationConfigApplicationContext 或 AnnotationConfigServletWebServerApplicationContext 等)来完成 Bean 的创建、事件发布以及容器的生命周期管理。
了解 Spring Boot 的加载过程和 Bean 加载过程,有助于我们排查各类启动期间的问题(例如循环依赖、初始化过程、Bean 破坏(destroy)等),并对 Spring Boot 原理有更深入的掌握。

img

2. Spring Boot 的整体启动流程与事件

Spring Boot 应用通常以 SpringApplication.run() 方法作为入口,其背后触发了多个事件和初始化过程。下面简单梳理下常见的核心事件与过程节点(以下事件顺序基于 Spring Boot 2.x/3.x 的通用流程,具体事件名或顺序在不同版本中可能会略有变化):

  1. ApplicationStartingEvent
    - 在运行 SpringApplication.run() 时最先发布。此时 SpringApplication 对象已创建,但还未开始读取配置或准备环境。

  2. ApplicationEnvironmentPreparedEvent
    - Spring Boot 开始准备并构建 Environment(包含 propertySources、profiles 等信息)。

  3. ApplicationContextInitializedEvent
    - 在 ApplicationContext 创建并准备初始化(但还没完成)时发布。此时已经确定要使用哪种类型的 ApplicationContext(web 环境则是 AnnotationConfigServletWebServerApplicationContext,普通 Java 程序则可能是 AnnotationConfigApplicationContext)。

  4. ApplicationPreparedEvent
    - Environment 已经准备好,且上下文(ApplicationContext)已经创建,但此时 Bean 还未加载完成。一些 BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor 可以在此阶段修改 Bean 定义信息。

  5. ContextRefreshedEvent
    - Spring 调用 refresh() 方法完成上下文刷新,触发 Bean 的创建与初始化,完成自动配置等。
    - Bean 的加载与初始化正是在 refresh() 的这个阶段中执行,随后所有单例 Bean 将被实例化并注入依赖。

  6. ApplicationStartedEvent
    - 上下文已刷新,但还没有调用 CommandLineRunner 和 ApplicationRunner 等接口方法。

  7. ApplicationReadyEvent
    - 所有 Bean 已加载完成,并且 CommandLineRunner、ApplicationRunner 已经执行完毕,应用已经可以对外提供服务。

  8. 容器运行中
    - 应用开始正常对外提供服务,接受请求。

  9. ContextClosedEvent(当应用关闭时)
    - 触发容器关闭事件,执行 Bean 的销毁方法及相关处理(如执行 DisposableBean、@PreDestroy 注解标注的方法),此后容器退出或等待关闭钩子(shutdown hook)。

整体而言,Spring Boot 的事件会贯穿整个启动和销毁过程,通过监听这些事件可进行一些自定义操作,比如在应用准备好时去加载特定资源,或者在关闭时做清理工作。


3. Spring 容器中 Bean 的加载与初始化流程

3.1 refresh() 方法中的关键流程

Spring Boot 在准备好环境后,会调用 AbstractApplicationContext#refresh() 来完成 BeanFactory 的创建、BeanDefinition 的加载和注册、BeanPostProcessor 的注册以及 Bean 的初始化等逻辑。其核心流程简要说明如下:

  1. 准备上下文环境
    - 为上下文设置一些容器标识、启动时间戳、监听器等。
  2. BeanFactory 初始化
    - 创建或获取 BeanFactory。在注解驱动场景中,通常是 AnnotationConfigApplicationContext 或 AnnotationConfigServletWebServerApplicationContext。
  3. 执行 BeanFactoryPostProcessor
    - 包括 ConfigurationClassPostProcessor 等,这里会扫描注解、解析配置类并注册更多的 BeanDefinition。
  4. 注册 BeanPostProcessor
    - 用于在 Bean 初始化前后插入自定义逻辑,比如自动注入、代理生成(AOP)等。
  5. 初始化消息源、事件广播器
    - 用于国际化消息处理及事件发布机制。
  6. 实例化所有剩余的单例 Bean
    - 此步骤中会正式调用 AbstractAutowireCapableBeanFactory#doCreateBean() 等方法进行 Bean 实例化、依赖注入和初始化。
  7. 完成刷新
    - 发布 ContextRefreshedEvent,此时容器初始化完成。

3.2 AbstractAutowireCapableBeanFactory#doCreateBean() 源码解析

在 Spring 创建 Bean 的时候,会调用 doCreateBean() 方法来完成实际的 Bean 实例化、属性注入和初始化。下面是你提供的 doCreateBean() 方法简要部分源码并作说明:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args)
        throws BeanCreationException {

    // 1. 实例化 Bean
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    // 2. 调用 MergedBeanDefinitionPostProcessor
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            mbd.markAsPostProcessed();
        }
    }

    // 3. 解决循环依赖(如果允许)
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
            && isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 4. 属性填充与初始化
    Object exposedObject = bean;
    try {
        // 填充属性(依赖注入,包含 @Autowired/@Resource 等)
        populateBean(beanName, mbd, instanceWrapper);
        // 调用 initializeBean,执行 BeanPostProcessor 的前后置处理,触发 @PostConstruct 等
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        // 处理异常
        throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
    }

    // 5. 如果有循环依赖,需要把早期引用替换回来
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // ...
        }
    }

    // 6. 注册销毁方法(DisposableBean 或 @PreDestroy 等)
    registerDisposableBeanIfNecessary(beanName, bean, mbd);

    return exposedObject;
}

从上面可以看出,doCreateBean() 核心阶段包括:

  1. 实例化:通过构造函数或工厂方法创建出 Bean 的原始对象。
  2. 后置处理(MergedBeanDefinitionPostProcessor):对合并后的 BeanDefinition 进行一些附加处理;
  3. 循环依赖处理:如果允许循环依赖,会提前放入 singletonFactories,让其他 Bean 注入时能获取到“早期”对象引用;
  4. 属性填充:执行真正的依赖注入,比如自动注入 @Autowired、@Resource;
  5. 初始化:包括执行 BeanPostProcessor#postProcessBeforeInitialization / @PostConstruct / InitializingBean#afterPropertiesSet / BeanPostProcessor#postProcessAfterInitialization;
  6. 注册销毁方法:如果实现了 DisposableBean 或声明了 @PreDestroy,会被记录下来在容器关闭时执行。

4. Bean 何时销毁,容器何时销毁?

4.1 容器关闭触发的时机

  • 正常关闭:在 Spring Boot 中,如果是正常退出(例如调用 applicationContext.close() 或者用户在 IDE/控制台按下停止),则会触发 ContextClosedEvent,随后按顺序销毁所有单例 Bean。
  • 异常关闭:如果进程被强制杀死(如 kill -9),则来不及回调 Spring 的钩子方法,Bean 的销毁方法将不会被执行。

Spring Boot 默认会注册一个 JVM 的 shutdown hook,当 JVM 退出时,会回调 close(),从而让容器有机会执行销毁逻辑。

4.2 Bean 的销毁过程

在 registerDisposableBeanIfNecessary() 中,Spring 会检查 Bean 是否实现了以下接口或注解:

  1. DisposableBean:如果实现了该接口,容器关闭时会调用其 destroy() 方法;
  2. @PreDestroy 注解:如果定义了该注解的方法,也会被调用;
  3. XML 配置中的 destroy-method:基于传统 XML 配置可以指定自定义销毁方法名称;

最后容器关闭时,会遍历所有已注册的销毁信息(DisposableBeanAdapter),并逐一调用这些销毁方法。


5. CommandLineRunner 和 ApplicationRunner 的执行时机

Spring Boot 在完成容器刷新(ContextRefreshedEvent 发布)后,会发布 ApplicationStartedEvent 和 ApplicationReadyEvent。而执行 CommandLineRunner 和 ApplicationRunner 的逻辑一般会在 ApplicationStartedEvent 之后、ApplicationReadyEvent 之前完成:

  • CommandLineRunner:传入 String... args 参数;
  • ApplicationRunner:传入 ApplicationArguments 参数。

也就是说,这些 Runner 接口的执行点是在容器的所有单例 Bean 已经初始化完毕 之后。若需要在容器刚刚就绪时执行一些初始化任务或者数据加载操作,则可以把逻辑放到 Runner 中。


6. 小结

Spring Boot 的加载过程可以概括为:
1. 创建并启动 SpringApplication;
2. 发布初始事件(ApplicationStartingEvent 等);
3. 准备并创建 ApplicationContext 环境(ApplicationEnvironmentPreparedEvent、ApplicationContextInitializedEvent);
4. 加载配置、注册 BeanDefinition,调用各种 Processor 以及后置处理器(ApplicationPreparedEvent、ContextRefreshedEvent);
5. 初始化所有单例 Bean(doCreateBean() 等过程:实例化 - 属性注入 - 初始化 - 注册销毁方法);
6. 执行 Runner(CommandLineRunner、ApplicationRunner);
7. 发布 ApplicationReadyEvent,应用对外提供服务;
8. 运行中,等待关闭钩子或主动调用 context.close();
9. 触发容器关闭,发布 ContextClosedEvent,执行 Bean 的销毁逻辑(DisposableBean、@PreDestroy 等);
10. 容器结束运行。

License:  CC BY 4.0