【重写SpringFramework】第一章beans模块:Bean的销毁(chapter 1-9)

1.前言

Bean 的生命周期包括初始化和销毁操作,上节介绍了 Bean 初始化流程,本节来看 Bean 的销毁流程是如何实现的。在实际应用中,绝大多数对象并不需要执行销毁操作,但某些对象本身管理着一定的资源。当 Spring 容器关闭时,所有的对象都会被虚拟机回收。在此之前,这些特殊的对象需要执行销毁逻辑,释放已有的资源,从而避免内存泄漏等问题。

2. 整体分析

2.1 销毁方式

Bean 的初始化和销毁是一对相反的操作,具有一定的相似性。二者都有三种实现方式,且执行顺序和适用场景也是一样的。简单介绍一下销毁方式,如下所示:

  1. 实现 DisposableBean 接口
  2. 在方法上声明 @PreDestroy 注解,该注解是由 JDK 提供的
  3. 设置 AbstractBeanDefinition 类的 destroyMethodName 属性

2.2 自动销毁组件

JDK 提供了 AutoCloseable 接口,作用是为某些特殊对象提供销毁逻辑。这些对象大多是资源的持有者,所谓的资源是一个广义的概念,包括文件、流、网络套接字等。下图列出了 AutoCloseable 接口的继承体系,涵盖了众多接口和实现类。常用组件如下所示:

  • 代表 IO 流的 Closesable 接口,包括各种流以及网络套接字
  • 与数据库有关的 ConnectionResultSetStatement 接口

在这里插入图片描述

按理来说,这些对象应该在适当的时机关闭,比较典型的是 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 作为适配器对象负责实际的销毁逻辑,我们可以从该类所持有的字段得到一些信息。

  • destroyMethodNamedestroyMethod 字段表示指定的销毁方法
  • 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 接口。一个方法是否被认为是默认的销毁方法,需要满足以下几个条件:

  • 不能指定自定义销毁方法,即 BeanDefinitiondestroyMethodName 属性必须为空
  • 必须是 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,然后由容器来触发销毁操作。这两个阶段是割裂开来的,我们先来看第一阶段的操作。回到 AbstractAutowireCapableBeanFactorydoCreateBean 方法,最后一步就是将需要销毁的单例注册到 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 接口,以及 BeanDefinitiondestroyMethodName 属性是否存在
  • 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 方法逐一处理。接下来分为三步:

  1. 从缓存中移除单例
  2. 获取适配器对象,这里用的是 remove 方法,同时将适配器对象也从缓存中移除了
  3. 执行单例的销毁流程,由适配器对象完成
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("执行默认的销毁方法...");
    }
}

测试方法比较简单,需要注意的是,我们没有指定 BeanDefinitiondestroyMethodName 属性,而是由适配器来推断默认的销毁方法。

//测试方法
@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 接口的销毁方法
  • 调用指定的销毁方法,分为两种情况。一是自定义的销毁方法,即 BeanDefinitiondestroyMethodName 属性。二是默认的销毁方法,即 AutoCloseable 接口实现类的 closeshutdown 方法。

在这里插入图片描述

之前也提到了 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编程探微】,加群一起讨论。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值