Ohhnews

分类导航

$ cd ..
Baeldung原文

Spring框架中的Bean后台初始化

#spring框架#bean初始化#后台初始化#异步处理#容器启动

1. 引言

Spring 框架中的 Bean 后台初始化提供了一种在应用上下文启动期间异步初始化选定 Bean 的方式。Spring 不再让每个 Bean 的初始化阻塞启动过程,而是将选定的 Bean 在后台初始化,同时继续处理应用上下文的其余部分。这能缩短应用启动时间,并降低包含昂贵初始化逻辑的应用程序中的感知延迟。

在本教程中,我们将探讨如何使用 Spring 原生的 bootstrap 属性实现 Bean 后台初始化。我们将演示如何正确配置它、Spring 如何在异步初始化期间管理生命周期一致性,以及这种方法如何提升依赖于缓存、外部服务或计算密集型组件等厚重资源的真实应用中的启动性能。

2. 为什么后台初始化在 Spring 中很重要

Spring 在 ApplicationContext 刷新阶段以严格同步的方式初始化 单例 Bean。启动期间,容器管理完整的 Bean 生命周期,并按顺序处理每个 Bean:

  1. Bean 实例化
  2. 依赖注入
  3. 初始化回调执行
  4. 将 Bean 标记为就绪可用

这种方法通过确保每个 Bean 在上下文刷新继续之前达到完全初始化状态,提供了可预测且确定性的启动行为。然而,当 Bean 在初始化期间执行昂贵操作时,它就会引入性能限制。

2.1. 同步 Bean 初始化模型

昂贵的初始化会增加 ApplicationContext 刷新阶段的持续时间,因为它强制容器在继续前进之前完成每个 Bean 的生命周期。这会降低 Bean 创建流水线的吞吐量,并增加整体启动延迟。结果,即使是非关键的初始化逻辑也会延迟应用程序变为可用。

2.2. 单线程初始化瓶颈

默认启动模型在同一线程上执行所有 Bean 初始化,该线程也驱动上下文刷新过程。因此,Spring 会顺序执行独立的初始化任务,阻止它们并发执行。这限制了启动吞吐量,并阻止 Spring 在应用启动时利用可用的并行性。

3. 后台初始化机制

Spring 使用 bootstrap 属性引入了后台初始化,以减少由昂贵的单例 Bean 创建引起的启动阻塞。

在应用上下文刷新期间,Spring 对选定的 Bean 应用修改后的执行策略:

  • 异步委托:Spring 将选定 Bean 的初始化阶段委托给容器管理的执行器(bootstrap executor),使其能在单独的线程上执行,而不阻塞主刷新过程。
  • 依赖协调:当另一个 Bean 通过非懒加载依赖依赖于一个后台初始化的 Bean 时,Spring 会挂起该 Bean 的依赖解析,并在注入该 Bean 之前等待初始化完成。
  • 生命周期保留:Spring 保留标准的生命周期顺序。

这种设计在将重量级初始化与主启动线程解耦的同时,确保了安全的后台执行,使应用能更早准备就绪,且不影响 Bean 的一致性。

4. 示例设置

本节介绍一个产品预加载类,模拟 Spring 应用中昂贵的启动工作负载。这可以是缓存预热、数据集加载或外部资源准备,这些通常会增加应用启动时间。

让我们定义这个类来演示该行为:

$ java
public class ProductCatalogInitializer {
    public ProductCatalogInitializer() {
        loadProducts();
    }
    private void loadProducts() {
        System.out.println("Starting product preload");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Product preload completed");
    }
    public String getStatus() {
        return "Catalog ready";
    }
}

loadProducts() 方法代表 Bean 创建期间执行的一个长时间运行的初始化任务。Spring 在主启动线程上运行此逻辑,这会阻塞 ApplicationContext 刷新直到完成。

该组件作为基准,用于演示 Spring 如何通过下一节的 bootstrap 机制将此类初始化工作转移到后台执行。

5. 使用 Bootstrap 模式的后台初始化

Spring 在 Bean 定义级别使用 bootstrap 属性应用后台初始化。选定的 Bean 随后被并发初始化,而不阻塞主启动线程。

5.1. 启用后台初始化

现在,让我们创建一个配置,将 ProductCatalogInitializer 标记为 Spring 管理的 Bean,并使用 BACKGROUND bootstrap 模式启用后台初始化:

$ java
@Configuration
public class AppConfig {
    @Bean(bootstrap = Bean.Bootstrap.BACKGROUND)
    public ProductCatalogInitializer productCatalogInitializer() {
        return new ProductCatalogInitializer();
    }
}

Spring 检测到 BACKGROUND bootstrap 模式,并将 Bean 初始化卸载到容器管理的 bootstrapExecutor,使其能在单独的线程上运行,而不是阻塞主应用上下文刷新。因此,该机制依赖于已配置的 bootstrap 执行器。如果没有它,Spring 无法执行后台初始化,受影响的 Bean 将在启动期间回退到正常的同步初始化。

5.2. Bean 注入

后台初始化的 Bean 可以像任何标准单例 Bean 一样注入到其他 Spring 管理的组件中,因为后台初始化不会改变核心依赖注入模型。

在大多数情况下,直接注入仍然足够,因为 Spring 会协调生命周期,并确保 Bean 在应用上下文启动阶段完全可用。然而,在访问可能发生在初始化过程完成之前的场景中,Spring 提供了 ObjectProvider 作为在运行时延迟检索 Bean 的可选机制。

为此,让我们创建一个服务,演示后台初始化 Bean 的注入:

$ java
@Service
public class ProductService {
    private final ObjectProvider<ProductCatalogInitializer>
            initializerProvider;
    public ProductService(
            ObjectProvider<ProductCatalogInitializer> initializerProvider) {
                this.initializerProvider = initializerProvider;
    }
    public void printStatus() {
        ProductCatalogInitializer initializer = initializerProvider.getObject();
        System.out.println("Bean status: " + initializer.getStatus());
    }
}

现在,依赖不是在对 Bean 创建时解析。相反,Spring 保留引用,并推迟到调用 getObject() 时才解析。此时,实际的 Bean 实例被检索,因此只在后台初始化完成后才发生访问,而注入阶段与启动时间无关。

5.3. 启用后台初始化的应用启动行为

当启用 BACKGROUND bootstrap 模式时,Spring 将选定的 Bean 初始化卸载到后台线程,允许应用上下文刷新继续而不等待这些 Bean。结果,对于选定的初始化工作负载,启动变为非阻塞,并且执行并行继续。

尽管如此,非懒加载的后台初始化 Bean 仍然是 Spring 容器生命周期的一部分,它们的完成会在整体启动过程中协调。

5.4. 性能影响

这种行为直接减少了启动延迟。Spring 将重量级 Bean 初始化从主刷新线程上移走。结果,应用程序可以更早地开始运营,而后台任务继续并行执行。加载大型缓存、建立连接池或运行模式验证的 Bean 受益最多。

6. 结论

在本文中,我们探讨了 Spring 框架中使用 BACKGROUND bootstrap 模式进行后台初始化的方式,该模式允许选定的 Bean 在应用上下文刷新期间异步初始化。

该机制将昂贵的 Bean 初始化与主启动线程解耦,同时保留了 Spring 生命周期语义。Spring 不是启动不受控制的线程,而是完全感知 Bean 依赖。如果一个非懒加载的 Bean 依赖于仍在后台初始化的 Bean,Spring 会确保它等待,防止访问不完整或部分构建的对象。此外,使用 ObjectProvider 有助于将 Bean 检索延迟到实际需要时,为早期访问场景增加额外的安全层。

这种方法通过允许在上下文刷新期间并行执行,提高了具有重量级初始化工作负载的系统的启动响应能力。它保持了依赖安全性和生命周期一致性,同时减少了主线程上的阻塞。后台初始化最适合非关键 Bean,如缓存预热器、外部集成和数据预加载组件。

本文的示例代码可在 Github 上获取

Bean Background Initialization in Spring Framework 一文最初发布于 Baeldung