1. 引言

本文将深入探讨Spring框架如何处理原型bean及其生命周期管理。掌握bean作用域的使用技巧是开发中必备的实用技能。我们将重点分析:是否需要手动销毁原型bean、何时需要以及具体实现方法。

尽管Spring提供了多种bean作用域,但本文将聚焦于原型作用域。

2. 原型bean及其生命周期

作用域决定了bean的生命周期和可见性。根据定义的作用域,IoC容器负责管理bean的生命周期。原型作用域规定:每次通过getBean()请求或注入其他bean时,容器都会创建一个全新的bean实例。 对于创建和初始化阶段,我们可以完全信任Spring。但销毁流程则完全不同。

在讨论销毁必要性前,先看如何定义原型bean:

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeExample {
}

3. 原型bean需要手动销毁吗?

Spring不会自动销毁原型bean。与单例作用域(IoC容器管理完整生命周期)不同,原型bean在实例化、配置和组装后,容器就不再跟踪其状态。

在Java中,当对象不再被任何引用可达时,就会成为垃圾回收的目标。通常让原型bean实例在使用后被垃圾回收器处理就足够了。 简单说:大多数场景下无需手动销毁原型bean。

但需要警惕这些场景:当bean处理文件、数据库连接或网络等资源时。由于原型作用域每次使用都会创建新实例,意味着资源会被持续消耗。长期积累可能导致内存泄漏或连接池耗尽等问题。 因为我们从未释放这些资源,只是不断创建新实例。

⚠️ 踩坑提醒:使用原型bean时,必须确保在使用后正确销毁,关闭所有创建或使用的资源。

4. 如何销毁原型bean?

Spring提供了多种手动销毁bean的方式。注意:如果同时使用多种机制,容器会依次执行它们,但至少需选择一种。

*所有示例(除自定义方法外)都需要手动调用BeanFactorydestroyBean()方法**。我们从ApplicationContext获取BeanFactory*并执行销毁:

applicationContext.getBeanFactory().destroyBean(prototypeBean);

4.1. 使用*@PreDestroy*注解

@PreDestroy注解用于标记bean的销毁方法。该方法必须无参数且非静态:

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

4.2. DisposableBean接口

DisposableBean接口包含唯一的回调方法*destroy()*。Spring官方不推荐此方式,因为它会强耦合代码到Spring框架。实现示例:

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

4.3. DestructionAwareBeanPostProcessor接口

与其他BeanPostProcessor变体类似,DestructionAwareBeanPostProcessor可定制bean的初始化逻辑。关键区别在于它增加了在销毁前执行自定义逻辑的方法。

实现步骤:

  1. 确保bean有资源释放机制(如前例的DisposableBean或自定义方法)
  2. 实现接口并调用销毁方法:
@Component
public class CustomPostProcessor implements DestructionAwareBeanPostProcessor {
    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (bean instanceof PostProcessorBeanExample) {
            ((PostProcessorBeanExample) bean).destroy();
        }
    }
}

4.4. POJO的自定义方法

当需要将POJO定义为原型bean时,可在定义时通过destroyMethod属性指定销毁方法:

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

@Configuration
public class DestroyMethodConfig {

    @Bean(destroyMethod = "destroy")
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public CustomMethodBeanExample customMethodBeanExample() {
        return new CustomMethodBeanExample();
    }
}

⚠️ 重要提醒:虽然我们成功标记了自定义销毁方法,但容器永远不会自动调用它! 因为容器只会对完全控制生命周期的bean执行销毁回调。这种场景下,需结合DestructionAwareBeanPostProcessor使用,或在使用原型bean后直接调用自定义销毁方法。

5. 总结

本文分析了Spring如何处理原型bean的初始化,却将销毁责任交给客户端。

虽然多数情况下无需手动销毁原型bean,但当它们处理文件、数据库连接或网络等资源时,强烈建议手动销毁。 因为每次请求都会创建新实例,资源消耗会迅速累积。为避免内存泄漏等问题,必须主动释放资源。

我们学习了四种销毁方式:*@PreDestroy注解、DisposableBean接口、DestructionAwareBeanPostProcessor接口和自定义方法。简单粗暴的选择建议:优先使用@PreDestroy,避免Spring强耦合;复杂场景可考虑DestructionAwareBeanPostProcessor*。

完整代码示例请查看GitHub仓库


原始标题:Do Spring Prototype Beans Need to Be Destroyed Manually? | Baeldung