1.前言
Bean 的生命周期包括初始化和销毁操作,上节介绍了 Bean 初始化流程,本节来看 Bean 的销毁流程是如何实现的。在实际应用中,绝大多数对象并不需要执行销毁操作,但某些对象本身管理着一定的资源。当 Spring 容器关闭时,所有的对象都会被虚拟机回收。在此之前,这些特殊的对象需要执行销毁逻辑,释放已有的资源,从而避免内存泄漏等问题。
2. 整体分析
2.1 销毁方式
Bean 的初始化和销毁是一对相反的操作,具有一定的相似性。二者都有三种实现方式,且执行顺序和适用场景也是一样的。简单介绍一下销毁方式,如下所示:
- 实现
DisposableBean
接口 - 在方法上声明
@PreDestroy
注解,该注解是由 JDK 提供的 - 设置
AbstractBeanDefinition
类的destroyMethodName
属性
2.2 自动销毁组件
JDK 提供了 AutoCloseable
接口,作用是为某些特殊对象提供销毁逻辑。这些对象大多是资源的持有者,所谓的资源是一个广义的概念,包括文件、流、网络套接字等。下图列出了 AutoCloseable
接口的继承体系,涵盖了众多接口和实现类。常用组件如下所示:
- 代表 IO 流的
Closesable
接口,包括各种流以及网络套接字 - 与数据库有关的
Connection
、ResultSet
、Statement
接口
按理来说,这些对象应该在适当的时机关闭,比较典型的是 IO 流使用。示例代码如下,创建 FileInputStream
对象打开一个文件,然后读取文件的内容,最后在 finally 块中关闭输入流,确保释放资源。
//示例代码
public void read(String path) {
FileInputStream is = null;
try {
is = new FileInputStream(path);
byte[] buff = new byte[is.available()];
is.read(buff);
System.out.println("读取内容: " + new String(buff));
} finally {
if (is != null) {
is.close(); //关闭输入流
}
}
}
现在考虑一种特殊情况,不管是出于疏忽或其他原因,本该销毁的对象却没有指定销毁方式。如果不执行销毁逻辑,可能会造成内存泄漏等问题。换个角度,从 AutoCloseable
接口语义来说,即使这些对象没有显式声明销毁方法,也应该执行对应的销毁方法。鉴于此,Spring 对 AutoCloseable
接口进行了兼容,具体是通过推断销毁方法实现的,详见下文。
2.3 工作原理
三种销毁方式涵盖了四种情况,Spring 使用适配器对象来统一处理销毁操作。具体来说,在创建对象时保存了两份实例,除了单例本身被注册到缓存外,还需要将一个适配器对象注册到待销毁的集合。当执行销毁操作时,从待销毁的集合中取出适配器对象,然后执行相应的销毁操作。
3. DisposableBeanAdapter
3.1 基本情况
DisposableBeanAdapter
本质上是一个包装对象,bean
字段表示待销毁的实例。DisposableBeanAdapter
作为适配器对象负责实际的销毁逻辑,我们可以从该类所持有的字段得到一些信息。
destroyMethodName
和destroyMethod
字段表示指定的销毁方法beanPostProcessors
字段用于处理声明注解的销毁方法DisposableBean
接口的实现类不需要特殊手段,通过 instanceof 关键字进行判断即可
public class DisposableBeanAdapter implements DisposableBean {
private final Object bean; //待销毁的实例
private final String beanName;
private String destroyMethodName;
private transient Method destroyMethod;
private List<DestructionAwareBeanPostProcessor> beanPostProcessors;
public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition, List<BeanPostProcessor> postProcessors) {
this.bean = bean;
this.beanName = beanName;
//推断销毁方法的名称
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
if(destroyMethodName != null){
this.destroyMethodName = destroyMethodName;
this.destroyMethod = findDestroyMethod();
}
this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
}
3.2 推断销毁方法
在 DisposableBeanAdapter
的构造方法中,inferDestroyMethodIfNecessary
的作用是推断销毁方法的名称。除了 BeanDefinition
中指定的销毁方法外,Spring 还要兼容 JDK 的 AutoCloseable
接口。一个方法是否被认为是默认的销毁方法,需要满足以下几个条件:
- 不能指定自定义销毁方法,即
BeanDefinition
的destroyMethodName
属性必须为空 - 必须是
AutoCloseable
接口的实现类,同时排除DisposableBean
接口的实现类 - 方法名是 close 或 shutdown,且方法必须是公开的、无参的。(此处精简了实现逻辑,只对方法名进行检查)
//推断销毁方法的名称
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
//case-1: 指定的销毁方法
String destroyMethodName = beanDefinition.getDestroyMethodName();
//case-2: 默认的销毁方法
//1) 不能指定自定义销毁方法
//2) 必须是AutoCloseable接口的实现类
if(destroyMethodName == null && closeableInterface.isInstance(bean)){
//不能是DisposableBean接口的实现类
if(!(bean instanceof DisposableBean)){
try {
//3) 方法名是close或shutdown
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
} catch (NoSuchMethodException e) {
try {
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
} catch (NoSuchMethodException ex) {
//ignore
}
}
}
return null;
}
return destroyMethodName;
}
我们发现,destroyMethodName
字段同时兼具两种用途,要么是指定的销毁方法,要么是默认的销毁方法。现在的问题是,常规的三种销毁方式可以共存,为什么这两种实现不能同时生效?对 Spring 框架来说,AutoCloseable
接口是「无意识」的,所谓无意识是指 Spring 认为用户可能遗漏掉销毁操作,这是悲观的预期。而 DisposableBean
接口是有意为之的,Spring 有理由认为用户会妥善处理,这是乐观的预期。从理论上来说,这种互相矛盾的现象不应该出现。
3.3 销毁操作
DisposableBeanAdapter
实现了 DisposableBean
接口的 destroy
方法,按照三种销毁方式依次执行。先调用声明了 @PreDestroy
注解的方法,再处理实现了 DisposableBean
接口的类,最后调用自定义或默认的销毁方法。
@Override
public void destroy() throws Exception {
//调用声明了@PreDestroy注解的方法
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
//调用DisposableBean接口
if(this.bean instanceof DisposableBean){
((DisposableBean) bean).destroy();
}
//调用自定义或默认的销毁方法
if (this.destroyMethod != null) {
ReflectionUtils.makeAccessible(this.destroyMethod);
destroyMethod.invoke(this.bean, (Object[]) null);
}
}
第一种销毁方式是由 InitDestroyAnnotationBeanPostProcessor
组件完成的,postProcessBeforeDestruction
方法与初始化的流程类似。先获取当前对象的生命周期元数据,然后通过反射的方式调用销毁方法。
//所属类[cn.stimd.spring.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor]
//处理@PreDestroy注解
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
//查找init和destroy方法的元数据
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
//调用destroy方法
metadata.invokeDestroyMethods(bean, beanName);
}
4. 销毁流程
4.1 注册待销毁 Bean
销毁流程分为两个阶段,首先在创建流程的最后注册待销毁的 Bean,然后由容器来触发销毁操作。这两个阶段是割裂开来的,我们先来看第一阶段的操作。回到 AbstractAutowireCapableBeanFactory
的 doCreateBean
方法,最后一步就是将需要销毁的单例注册到 Spring 容器中。
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//创建对象
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
//1. Bean的实例化(略)
//2. 提前暴露Bean解决循环依赖(TODO)
//3. 属性填充(略)
//4. 初始化(略)
//5. 注册需要destroy方法的Bean
registerDisposableBeanIfNecessary(beanName, bean, mbd);
return exposedObject;
}
registerDisposableBeanIfNecessary
方法负责注册需要销毁的 Bean。首先判断 Bean 是否需要被销毁,这里用到了 DisposableBeanAdapter
的两个静态方法。
hasDestroyMethod
方法判断是否实现了DisposableBean
接口或者AutoCloseable
接口,以及BeanDefinition
的destroyMethodName
属性是否存在hasApplicableProcessors
方法检查是否存在声明@PreDestroy
注解的方法
只要满足上述四个条件之一,就认为是一个待销毁的单例。然后将待销毁的单例包装成一个 DisposableBeanAdapter
对象,并将适配器对象注册到 Spring 容器中。
//所属类[cn.stimd.spring.beans.factory.support.AbstractAutowireCapableBeanFactory]
//注册需要销毁的Bean
private void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
//判断当前对象是否需要销毁
if(bean != null && DisposableBeanAdapter.hasDestroyMethod(bean, mbd) ||
DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessors())){
DisposableBeanAdapter disposableBean = new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors());
//将适配器对象注册到容器中
registerDisposableBean(beanName, disposableBean);
}
}
registerDisposableBean
方法是由 DefaultSingletonBeanRegistry
实现的,待销毁的单例将被注册到 disposableBeans
字段中。
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
//待销毁Bean集合
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}
}
4.2 触发销毁
注册待销毁单例的执行时机是固定的,就是整个创建流程的最后一步,什么时候销毁则取决于 Spring 容器何时关闭。我们暂时不用关心容器关闭的问题,无论如何总是需要通过回调方法来触发。ConfigurableBeanFactory
接口定义了销毁单例的方法,DefaultListableBeanFactory
实现了该方法,实际上委托给父类 DefaultSingletonBeanRegistry
的同名方法处理。
public interface ConfigurableBeanFactory{
//销毁所有单例
void destroySingletons();
}
在 DefaultSingletonBeanRegistry
类的 destroySingletons
方法中,首先遍历待销毁单例的集合,然后交由 destroySingleton
方法逐一处理。接下来分为三步:
- 从缓存中移除单例
- 获取适配器对象,这里用的是
remove
方法,同时将适配器对象也从缓存中移除了 - 执行单例的销毁流程,由适配器对象完成
public class DefaultSingletonBeanRegistry {
//销毁所有的单例
public void destroySingletons() {
String[] disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
}
public void destroySingleton(String beanName) {
//从缓存中移除单例
removeSingleton(beanName);
//获取适配器对象,同时从待销毁的集合中
DisposableBean disposableBean;
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
}
//销毁单例
destroyBean(beanName, disposableBean);
}
protected void destroyBean(String beanName, DisposableBean bean) {
if(bean != null){
//此处的bean实际是单例对应的DisposableBeanAdapter,由适配器来完成具体的销毁逻辑
bean.destroy();
}
}
}
5. 测试
5.1 销毁操作
测试类 MyDestroyBean
实现了三种销毁方式,close
方法声明注解,destroy
方法实现接口,customDestroy
是自定义的销毁方法。
//测试类:实现了三种方式的销毁
public class MyDestroyBean implements DisposableBean {
@PreDestroy
private void close(){
System.out.println("销毁 @PreDestroy注解方法...");
}
@Override
public void destroy() throws Exception {
System.out.println("销毁 DisposableBean接口方法...");
}
public void customDestroy(){
System.out.println("销毁 自定义destroy方法...");
}
}
测试方法需要注意三点,一是向 BeanFactory
添加 InitDestroyAnnotationBeanPostProcessor
组件来处理 @PreDestroy
注解。二是创建 RootBeanDefinition
对象后,需要调用 setDestroyMethodName
方法指定销毁方法的名称。三是需要调用 destroySingletons
方法,主动触发销毁逻辑。
//测试方法
@Test
public void testDestroyMethod(){
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//添加InitDestroyAnnotationBeanPostProcessor来处理@PreDestroy注解
factory.addBeanPostProcessor(new InitDestroyAnnotationBeanPostProcessor());
RootBeanDefinition definition = new RootBeanDefinition(MyDestroyBean.class);
//指定自定义销毁方法
definition.setDestroyMethodName("customDestroy");
factory.registerBeanDefinition("destroyBean", definition);
factory.preInstantiateSingletons();
//触发销毁逻辑
factory.destroySingletons();
}
从测试结果中可以看到,不同销毁方式的执行顺序。先是注解声明的方法,然后是接口方法,最后是自定义的销毁方法。
销毁 @PreDestroy 注解方法...
销毁 DisposableBean 接口方法...
销毁自定义 destroy 方法...
5.2 自动销毁
测试类 MyAutoCloseBean
实现了 AutoCloseable
接口,重写 close
方法并打印日志。
//测试类
public class MyAutoCloseBean implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("执行默认的销毁方法...");
}
}
测试方法比较简单,需要注意的是,我们没有指定 BeanDefinition
的 destroyMethodName
属性,而是由适配器来推断默认的销毁方法。
//测试方法
@Test
public void testAutoCloseable() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("closeBean", new RootBeanDefinition(MyAutoCloseBean.class));
factory.preInstantiateSingletons();
factory.destroySingletons();
}
从测试结果可以看到,close
方法执行成功,说明 Spring 对需要自动销毁的组件提供了支持。
执行默认的销毁方法...
6. 总结
Bean 的销毁流程包括两个阶段,首先在创建实例的最后阶段,将待销毁的单例包装成 DisposableBeanAdapter
适配器对象,并注册到 Spring 容器中。其次,调用 ConfigurableBeanFactory
接口的 destroySingletons
方法触发销毁流程。单例的销毁操作是由 DisposableBeanAdapter
适配器对象完成的,一共处理了四种情况。如下所示:
- 通过
InitDestroyAnnotationBeanPostProcessor
组件处理声明了@PreDestroy
注解的方法 - 调用
DisposableBean
接口的销毁方法 - 调用指定的销毁方法,分为两种情况。一是自定义的销毁方法,即
BeanDefinition
的destroyMethodName
属性。二是默认的销毁方法,即AutoCloseable
接口实现类的close
或shutdown
方法。
之前也提到了 Spring 框架对 JDK 的兼容,比如 @Inject
注解、Provider
接口等,但都一句话带过了。原因在于这些 API 是可选的,都有同位替代。AutoCloseable
接口比较特殊,持有一定的资源,如果处理不善可能引发意想不到的后果。用户使用 AutoCloseable
对象很正常,忘记指定销毁逻辑也可以理解,但这两种情况同时出现就是比较极端的场景了。用户可以出错,但框架应该尽量提供兜底措施。这里考验的是一个框架的完备性,魔鬼往往藏在细节之中,我们学习框架除了具体的技巧外,更要重视这种思维方式。
7. 项目信息
新增修改一览,新增(4),修改(7)。
beans
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.spring.beans
│ └─ factory
│ ├─ annotation
│ │ └─ InitDestroyAnnotationBeanPostProcessor.java (*)
│ ├─ config
│ │ ├─ ConfigurableBeanFactory.java (*)
│ │ └─ DestructionAwareBeanPostProcessor.java (*)
│ ├─ support
│ │ ├─ AbstractAutowireCapableBeanFactory.java (*)
│ │ ├─ DefaultListableBeanFactory.java (*)
│ │ ├─ DefaultSingletonBeanRegistry.java (*)
│ │ └─ DisposableBeanAdapter.java (+)
│ └─ DisposableBean.java (+)
└─ test
└─ java
└─ beans
└─ lifecycle
├─ LifecycleTest.java (*)
├─ MyAutoCloseBean.java (+)
└─ MyDestroyBean.java (+)
注:+号表示新增、*表示修改
-
项目地址:https://gitee.com/stimd/spring-wheel
-
本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter1-9
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
关注公众号【Java编程探微】,加群一起讨论。