Spring Prototype Bean 需要手动销毁吗?

1、简介

本文将带你了解 Spring 如何处理 Prototype Bean 并管理其生命周期,主要介绍是否有必要手动销毁 Prototype Bean、何时销毁以及如何销毁。

Spring 提供了多种 Bean Scope,本文主要聚焦 Prototype。

2、Prototype Bean 及其生命周期

Scope(作用域)确定了 Bean 在其存在的上下文中的生命周期和可见性。根据 Scope 的定义 ,IoC 容器负责管理 Bean 的生命周期。Prototype(原型)Scope 指示容器在每次使用 getBean() 请求或注入到另一个 Bean 时创建一个新的 Bean 实例。在创建和初始化方面,可以放心地依赖于 Spring。然而,销毁 Bean 的过程则不同。

在了解销毁 Bean 的必要性之前,先看看如何创建一个 Prototype Bean,如下:

@Component
// 指定 Bean 的 Scope 为 Prototype
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeExample {
}

3、Prototype Bean 需要手动销毁吗?

Spring 不会自动销毁 Prototype Bean。与 Singleton Scope 不同,IoC 容器并不负责 Prototype Bean 整个的生命周期。容器会实例化、配置和组装 Prototype Bean,但之后将停止跟踪其状态

在 Java 中,当一个对象无法再通过任何引用到达时,它就符合垃圾回收(GC)的条件。通常,在使用完 Prototype Bean 实例后,让其等待 GC 即可。换句话说,在大多数使用情况下,不必费心销毁 Prototype Bean。

然后,也要考虑一下需要手动销毁 Bean 的情况。例如,在处理文件、数据库连接或网络等需要资源的流程时。由于 Prototype Scope 规定,我们每次使用 Bean 时都会创建它,这意味着资源也会被使用和消耗。因此,随着时间的推移,使用量的累积可能会导致 内存泄漏连接池耗尽 等潜在问题。之所以会出现这种情况,是因为我们从未释放过这些资源,而是通过使用 Prototype Bean 不断创建新的资源。

这种场景下,我们必须确保在使用 Prototype Bean 后正确地销毁它们,关闭创建或使用的所有资源。

4、如何销毁 Prototype Bean?

在 Spring 中手动销毁 Bean 有几种方法,你甚至可以同时使用多种方法。

每个示例都需要手动调用 BeanFactory 中的 destroyBean() 方法(除了自定义销毁方法),我们从 ApplicationContext 获取 BeanFactory 并调用 destroyBean

applicationContext.getBeanFactory().destroyBean(prototypeBean);

4.1、使用 @PreDestroy 注解

注解 @PreDestroy 用于标记 Bean 中负责销毁 Bean 的方法。方法不允许有任何参数,也不允许是静态的:

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PreDestroyBeanExample {
    @PreDestroy
    private void destroy() {
        // 释放 Bean 持有的所有资源
    }
}

4.2、DisposableBean 接口

实现 DisposableBean 接口,它有一个唯一的回调方法 destroy():。

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DisposableBeanExample implements DisposableBean {
    @Override
    public void destroy() {
        // 释放 Bean 持有的所有资源
    }
}

Spring 团队并不推荐使用 DisposableBean 接口,因为它会将代码耦合到 Spring 中。

4.3、DestructionAwareBeanPostProcessor 接口

DestructionAwareBeanPostProcessor(类似于其他 BeanPostProcessor 变体)用于自定义 Bean 的初始化过程。一个关键的区别是它包含一个额外的方法,在销毁 Bean 之前执行自定义逻辑。

在实现该接口之前,我们必须要有一种释放 Bean 资源的方式。我们可以使用DisposableBean,就像在前面的示例中一样,或者使用自定义方法。

下一步,实现该接口,在其中调用销毁方法:

@Component
public class CustomPostProcessor implements DestructionAwareBeanPostProcessor {
    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (bean instanceof DisposableBean) {
            ((DisposableBean) bean).destroy();
        }
    }
}

4.4、使用自定义方法的 POJO

我们可能会遇到这样一种情况:我们想将一个 POJO 定义为 Prototype Bean。

在定义 Bean 时,我们可以使用 @Bean 注解的 destroyMethod 属性来指定负责销毁 Bean 的特定方法:

public class CustomMethodBeanExample {
    public void destroy() {
        // 释放 Bean 持有的所有资源
    }
}

@Configuration
public class DestroyMethodConfig {

    @Bean(destroyMethod = "destroy") // 指定 Bean 的销毁方法
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public CustomMethodBeanExample customMethodBeanExample() {
        return new CustomMethodBeanExample();
    }
}

我们通过 destroyMethod 属性指定了 Bean 销毁时的自定义方法,但它永远不会被调用。这是因为容器只会对其生命周期完全受其控制的 Bean 调用该方法。在这种情况下,我们可以使用 DestructionAwareBeanPostProcessor 或在停止使用 Prototype Bean 时调用我们的自定义销毁方法。

5、总结

本文介绍了 Spring 如何处理 Prototype Bean 并管理其生命周期,以及几种可以用来销毁 Bean 的方法,包括 @PreDestroyDisposableBean 接口、DestructionAwareBeanPostProcessor 接口和自定义方法。

尽管手动销毁 Prototype(原型)Bean可能并不是必需的,但如果它们处理资源,如文件处理、数据库连接或网络操作,建议这样做。由于每次请求时都会创建 Prototype Bean 实例,资源会迅速累积。为了避免任何不必要的问题,如内存泄漏,我们必须释放资源。


Ref:https://www.baeldung.com/spring-manually-destroy-prototype-bean