虽然可能不需要手动销毁原型 bean,但如果它们处理文件处理、数据库连接或网络等资源,则建议这样做。由于每次请求时都会创建原型 bean 实例,因此资源会很快堆积起来。为了避免任何不必要的问题(例如内存泄漏),我们必须释放资源。
我们可以用来销毁 bean 的四种方法,包括@PreDestroy、DisposableBean接口、DestructionAwareBeanPostProcessor接口和自定义方法。
原型Bean及其生命周期
作用域决定了 bean 在其存在上下文中的生命周期和可见性。根据其定义的范围,IoC 容器负责管理 bean 的生命周期。
原型作用域规定,每次使用getBean()请求或注入另一个 bean时,容器都会创建一个新的 bean 实例。在创建和初始化的情况下,我们可以安全地依赖 Spring。但是,销毁 bean 的过程有所不同。
在检查销毁bean的必要性之前,我们先来看一下如何创建原型bean:
@Component |
原型Bean需要手动销毁吗?
Spring 不会自动销毁原型 bean。与单例范围不同,在单例范围内,IoC 容器负责 bean 的整个生命周期,而对于原型,情况并非如此。容器将实例化、配置和组装原型 bean,但随后它将停止跟踪其状态。
在 Java 中,当对象不再通过任何引用可访问时,它就符合垃圾回收条件。通常,在使用完原型 bean 实例后,将其保留下来以供垃圾回收器回收就足够了。换句话说,在大多数情况下,我们不必费心销毁原型 bean。
另一方面,让我们考虑建议手动销毁 bean 的情况。例如,在处理需要资源的进程(如处理文件、数据库连接或网络)时。由于原型范围规定每次使用 bean 时都会创建一个 bean,这意味着资源也会被利用和消耗。因此,随着时间的推移,使用量的积累可能会导致潜在的问题,例如内存泄漏和连接池耗尽。发生这种情况的原因是我们从不释放这些资源,我们只是使用原型 bean 不断创建新的资源。
这就是为什么我们必须确保在使用原型 bean 之后正确地销毁它们,并关闭我们创建或使用的所有资源。
如何销毁原型Bean?
有几种方法可以在 Spring 中手动销毁 bean。需要注意的是,如果我们使用多种机制,容器将应用每种机制,但我们至少需要使用一种。
每个示例都需要手动调用BeanFactory 中的destroyBean()方法,但自定义方法除外,我们可以调用自定义方法。我们将从ApplicationContext获取BeanFactory并调用 bean 销毁:
applicationContext.getBeanFactory().destroyBean(prototypeBean);
1. 使用@PreDestroy注解
注释@PreDestroy用于标记负责销毁 bean 的 bean 方法。方法不允许有任何参数,也不允许是静态的。我们将在实践中看到它是什么样子:
@Component |
2. DisposableBean接口
DisposableBean接口有一个回调方法destroy(),我们必须实现它。Spring 团队不建议使用DisposableBean接口,因为它将代码与 Spring 耦合在一起。不过,我们还是来看看如何使用它:
@Component |
3. DestructionAwareBeanPostProcessor接口
DestructionAwareBeanPostProcessor与其他BeanPostProcessor变体一样,可自定义 bean 的初始化。一个关键区别是,它包含一个额外的方法,用于在销毁 bean 之前执行自定义逻辑。
在实现接口之前,我们必须确保有办法从 bean 中释放资源。我们可以使用 DisposableBean(如上例所示)或自定义方法。
下一步是实现一个接口,我们将在其中调用我们的销毁方法:
@Component |
4. 使用 POJO 的自定义方法
可能存在这样的情况,我们有一个POJO,我们想将其定义为原型 bean。在定义 bean 时,我们可以使用属性destroyMethod来指定负责销毁 bean 的特定方法。让我们看看如何做到这一点:
public class CustomMethodBeanExample { |
我们成功地将自定义方法标记为destroyMethod回调,但它永远不会被调用。这是因为容器只对生命周期完全由它控制的 bean 调用它。在这种情况下,我们可以使用DestructionAwareBeanPostProcessor,或者在停止使用原型 bean 时简单地调用我们的自定义销毁方法。