Spring Bean 生命周期与原型作用域

最后更新: 2025-11-17 23:19:46 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

Spring 将 Bean 元数据与 Bean 实例分离。BeanDefinition 描述如何创建对象, 而 BeanFactory 会在请求对象时应用该定义。ApplicationContext 还提供协调启动、 后处理、事件、资源加载和关闭功能。

上下文刷新期间发生的事情 #

AbstractApplicationContext.refresh() 会在应用程序单例可以使用之前准备环境和 Bean 工厂。主要阶段如下:

  1. 准备或获取 BeanFactory 并加载 Bean 定义。
  2. 在定义仍可修改时调用 BeanFactoryPostProcessors,包括配置类处理。
  3. 发现并注册 BeanPostProcessors。
  4. 初始化上下文基础设施,例如消息解析、事件和上下文特定的资源。
  5. 实例化其余的非延迟初始化单例 Bean。
  6. 发布已刷新的状态和 ContextRefreshedEvent。

BeanFactoryPostProcessor 修改元数据。相比之下,BeanPostProcessor 会处理实例, 可以注入字段、调用生命周期注解,或将 Bean 替换为代理。由于普通应用程序 Bean 可能会在所有后处理器注册之前被实例化,因此从工厂后处理器中创建普通应用程序 Bean 是不安全的。

依赖顺序与 depends-on #

容器会先创建 Bean 的构造器依赖和属性依赖,再创建依赖它的 Bean。因此,构造器注入 同时表达了必需的关系和创建顺序:

java
@Component
final class ReportService {
    private final ReportRepository repository;

    ReportService(ReportRepository repository) {
        this.repository = repository;
    }
}

当不存在注入关系时,例如初始化共享外部资源,@DependsOn("schemaInitializer") 或 XML 中的 depends-on 可以添加顺序约束。对于单例 Bean,它还会反转销毁顺序: 依赖其他 Bean 的 Bean 会先于其依赖的 Bean 被销毁。depends-on 会隐藏类所使用的 依赖,因此不应替代实际的类型化依赖关系。

实例初始化 #

对于普通 Bean,Spring 会执行以下操作:

  1. 实例化对象,通常通过调用构造器或工厂方法完成。
  2. 解析并注入属性和字段。
  3. 调用 aware 回调,包括 BeanNameAware、BeanFactoryAware 和 ApplicationContextAware。
  4. 在初始化之前调用每个 BeanPostProcessor。
  5. 依次调用 @PostConstruct、InitializingBean.afterPropertiesSet(), 以及配置的自定义 init 方法。
  6. 在初始化之后调用每个 BeanPostProcessor。

Spring 基础设施通过后处理器实现该序列的部分步骤,因此自定义处理器必须遵守顺序约定。 初始化后的处理器可能返回代理,而不是原始对象。只有在整个处理链完成后,调用方才会 收到已准备就绪、可能经过增强的对象。

构造器注入是建立必需不变量最安全的位置。当初始化依赖注入的属性或构造器中不可用的 容器服务时,适合使用 @PostConstruct。

销毁与上下文关闭 #

关闭可配置的上下文会销毁受管理的单例。对于每个 Bean,Spring 会调用 @PreDestroy、 DisposableBean.destroy() 和配置的自定义 destroy 方法,但会遵循适配器的重复方法检查。 SmartLifecycle 为需要协调关闭的组件增加了分阶段的停止回调。

应用程序应关闭手动创建的上下文,或使用 registerShutdownHook() 注册关闭钩子。 Spring Boot 会在进程正常关闭的过程中关闭其应用程序上下文。进程突然终止时无法保证 回调执行,因此持久性正确性不能仅依赖这些回调。

原型的所有权 #

原型作用域意味着每次容器请求都会产生一个新实例。当原型直接注入单例时,只会在创建 单例期间解析一次;该字段会一直保留同一个原型实例。如果每次操作都需要新实例,请使用 ObjectProvider<T>、Jakarta Provider<T>、方法注入或作用域代理。

Spring 会完整初始化原型并将其返回给调用方,但不会保留该实例,也不会在之后调用销毁回调。 因此,所有权会转移给接收方。获取了持有文件、套接字或执行器的原型的代码必须显式关闭它。 如果需要集中清理,自定义 BeanPostProcessor 可以跟踪此类实例,但显式的资源所有权更清晰。

主要参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)