Spring Bean的生命周期(五) - 销毁阶段

bean 完整生命周期原理的最后一部分,我们来研究应用停止时,bean 的销毁阶段都有哪些动作和过程。

本章主要会涉及到的原理部分:

  • bean 销毁前 IOC 容器的处理

  • bean 销毁阶段的回调

在测试代码中,我们有主动调用 ApplicationContextstopclose 方法,这样 IOC 容器的关闭和销毁流程就会依次执行了。

    ctx.stop();
    System.out.println("================IOC容器停止成功==================");
    ctx.close();
}

先来看 stop 的内容。

1. ApplicationContext#stop

public void stop() {
    getLifecycleProcessor().stop();
    publishEvent(new ContextStoppedEvent(this));
}

呦,这个套路不正好跟上一章看到的 start 动作完全相反吗?这个 LifecycleProcessor 中的 stop 方法一定也是先给所有的 Lifecycle bean 分组,排序,依次调用 stop 方法。当然,我们的推测是绝对正确的,小伙伴们可以跟着源码进去看一看,小册就不贴出来了。

2. ApplicationContext#close

关闭容器的动作,仅仅只有一个 doClose 方法,那我们就往里进就好。不过这个 doClose 方法有点长,我们拆解开来讲解。

2.1 广播事件

这一段非常简单,广播 ContextClosedEvent 事件而已。前面在事件章节的讲解中我们也说过,SpringFramework 中一共内置了 4 个可供监听的预设事件,它们都是给开发者自己使用的,SpringFramework 内部是没有任何利用的。

2.2 LifecycleProcessor#onClose

等一下?这怎么又是 LifecycleProcessor ?该不会。。。它的 onClose 方法又是跟 stop 一样?看一眼源码:

wtf ???好吧,果然跟上面是一样一样的,那小册就不赘述啦。

2.3 destroyBeans - 销毁bean

这一部分我们只看这一个方法,不要觉得它简单哦,它的复杂度还是挺高的,我们进入来看。

实现倒也简单,仅仅是获取到 ApplicationContext 内部的 BeanFactory ,让它去销毁所有的单实例 bean 。

2.3.1 DefaultListableBeanFactory#destroySingletons

继续往里走,进入到 DefaultListableBeanFactory 中:

这段代码中首先会调用父类 DefaultSingletonBeanRegistrydestroySingletons 方法,随后会清空所有的单实例 bean 的名称,以及 “类型到 name ” 的映射。( allBeanNamesByType 中保存了 bean 的类型包含的所有 bean ,如 Person 类型的 bean 在 IOC 容器中包含 masteradmin

2.3.2 DefaultSingletonBeanRegistry#destroySingletons

重点还是在 DefaultSingletonBeanRegistry 中,进入它的 destroySingletons 方法实现:

这段源码看着挺长,核心就一句:重载的 destroySingleton(beanName) 。下面的好多 Mapclear 动作,以及 clearSingletonCache 方法,都是清除掉缓存而已,核心还是逐个销毁单实例 bean 。

2.3.3 destroySingleton

进入到单个 bean 的销毁流程,Debug 发现又回到 DefaultListableBeanFactory 了:

-.- 又来这一套是吧,合着销毁一个,跟销毁一堆的套路是一样一样的,那我们继续往里进,还是走到 DefaultSingletonBeanRegistry 中:

销毁单个 bean 的动作总共就两步:清空 **BeanFactory** 中用于处理循环依赖的缓存,回调 bean 的销毁动作。重要的方法仍然在最后,我们继续往里深入:

2.3.4 destroyBean

这个方法又是好长啊,我们还是拆解开来看。

销毁依赖的bean

首先,在销毁一个 bean 时,如果它有依赖其它的 bean ,则首要目标不是销毁自己,而是先销毁那些依赖的 bean ,所以这里会有递归调用上面 destroySingleton 方法的动作。

自定义bean销毁方法的回调

动作很简单,一个 destroy 方法就完事了,点击去看一下吧:

到这里我们就发现了,它会把我们常用的三种 bean 的销毁手段都依次执行一遍,同时也就解释了 bean 的销毁阶段生命周期回调的顺序:**@PreDestroy** **DisposableBean** **destroy-method**

处理bean中bean

这个概念不是很好理解吧,bean 中 bean 是什么鬼?还记得第 9 章中,我们在写复杂类型注入的时候,说可以在 property 中再写 <bean> 这回事吧,不记得也没有关系,我改一下原有的测试代码,想必你一下子就能想起来了吧:

哎,这种嵌套 bean ,就会记录在 DefaultSingletonBeanRegistrycontainedBeanMap 中,既然外头的 bean 要销毁了,那里头的这些 bean 也就应该被销毁了,所以这里又是取到那些内部构造好的 bean ,依次递归调用 destroySingleton 方法来销毁这些 bean 。

销毁被依赖的bean

注意这里的说法,这里是销毁那些依赖的 bean ,由于一个 bean 可能被其它 bean 引用或者依赖,所以在最后一步,当自己销毁之后,那些依赖了当前 bean 的 bean 也就应该被销毁。套路都是一样的,取到那些被依赖的 bean 的名称,依次递归调用 destroySingleton 方法销毁。

到这里,destroyBeans 销毁单实例 bean 的逻辑就全部走完了,继续往下看 doClose 方法还干了什么事。

2.4 关闭BeanFactory

这个步骤,说是关闭 BeanFactory ,实际上更接近于 “销毁” ,因为原来的 BeanFactory 无论如何都无法继续用了。

在基于 xml 和基于注解驱动的两种 ApplicationContext 的实现里,它们的策略既相似又不同:

在基于 xml 配置文件的 ApplicationContext 中,它会获取到原有的 BeanFactory ,移除序列化 ID ,并直接丢弃原来的 BeanFactory

在基于注解驱动的 ApplicationContext 中,它只会给内部组合的 BeanFactory 移除序列化 ID 而已。

2.4.1 GenericApplicationContext不允许被重复刷新的原因

这个时候可能会有小伙伴们产生疑惑:之前不是说 GenericApplicationContext 不允许重复刷新吗?既然它只是移除了一下序列化 ID ,那刷新的时候重新设置一个不就行了吗?哎,这个时候我们要了解 GenericApplicationContext 中的另外一个成员了:

看这里,如果当前 GenericApplicationContext 是刚创建的,那么 refreshed 的值一定是 false ,此时使用 CAS 设置 true 是成功的,下面的 throw ex 动作不会执行;而第二次再调用 refresh 刷新 ApplicationContext 时,进入到该方法时,CAS 不通过,无法刷新 BeanFactory ,最终抛出异常。

为什么 AbstractRefreshableApplicationContext 就没这事呢?因为 AbstractRefreshableApplicationContext 中本来就没这个 refreshed 的设计,而且人家在关闭 BeanFactory 的时候,直接把原来的 BeanFactory 扔了,不要了,那当然得支持刷新呀(不然 BeanFactory 都没了 ApplicationContext 就是一空壳)。

2.5 剩余动作

剩余的动作就不重要了,而且也没什么实在东西,我们就不关注了。

至此,ApplicationContext 关闭,bean 的销毁动作也就全部结束了。

3. 总结

bean 对象在销毁时,由 **ApplicationContext** 发起关闭动作。销毁 bean 的阶段,由 **BeanFactory** 取出所有单实例 bean ,并逐个销毁。

销毁动作会先将当前 bean 依赖的所有 bean 都销毁,随后回调自定义的 bean 的销毁方法,之后如果 bean 中有定义内部 bean 则会一并销毁,最后销毁那些依赖了当前 bean 的 bean 也一起销毁。

最后更新于

这有帮助吗?