最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
bean 完整生命周期原理的最后一部分,我们来研究应用停止时,bean 的销毁阶段都有哪些动作和过程。
本章主要会涉及到的原理部分:
bean 销毁前 IOC 容器的处理
bean 销毁阶段的回调
在测试代码中,我们有主动调用 ApplicationContext
的 stop
和 close
方法,这样 IOC 容器的关闭和销毁流程就会依次执行了。
先来看 stop
的内容。
呦,这个套路不正好跟上一章看到的 start
动作完全相反吗?这个 LifecycleProcessor
中的 stop
方法一定也是先给所有的 Lifecycle
bean 分组,排序,依次调用 stop
方法。当然,我们的推测是绝对正确的,小伙伴们可以跟着源码进去看一看,小册就不贴出来了。
关闭容器的动作,仅仅只有一个 doClose
方法,那我们就往里进就好。不过这个 doClose
方法有点长,我们拆解开来讲解。
这一段非常简单,广播 ContextClosedEvent
事件而已。前面在事件章节的讲解中我们也说过,SpringFramework 中一共内置了 4 个可供监听的预设事件,它们都是给开发者自己使用的,SpringFramework 内部是没有任何利用的。
等一下?这怎么又是 LifecycleProcessor
?该不会。。。它的 onClose
方法又是跟 stop
一样?看一眼源码:
wtf ???好吧,果然跟上面是一样一样的,那小册就不赘述啦。
这一部分我们只看这一个方法,不要觉得它简单哦,它的复杂度还是挺高的,我们进入来看。
实现倒也简单,仅仅是获取到 ApplicationContext
内部的 BeanFactory
,让它去销毁所有的单实例 bean 。
继续往里走,进入到 DefaultListableBeanFactory
中:
这段代码中首先会调用父类 DefaultSingletonBeanRegistry
的 destroySingletons
方法,随后会清空所有的单实例 bean 的名称,以及 “类型到 name ” 的映射。( allBeanNamesByType
中保存了 bean 的类型包含的所有 bean ,如 Person
类型的 bean 在 IOC 容器中包含 master
和 admin
)
重点还是在 DefaultSingletonBeanRegistry
中,进入它的 destroySingletons
方法实现:
这段源码看着挺长,核心就一句:重载的 destroySingleton(beanName)
。下面的好多 Map
的 clear
动作,以及 clearSingletonCache
方法,都是清除掉缓存而已,核心还是逐个销毁单实例 bean 。
进入到单个 bean 的销毁流程,Debug 发现又回到 DefaultListableBeanFactory
了:
-.- 又来这一套是吧,合着销毁一个,跟销毁一堆的套路是一样一样的,那我们继续往里进,还是走到 DefaultSingletonBeanRegistry
中:
销毁单个 bean 的动作总共就两步:清空 **BeanFactory**
中用于处理循环依赖的缓存,回调 bean 的销毁动作。重要的方法仍然在最后,我们继续往里深入:
这个方法又是好长啊,我们还是拆解开来看。
销毁依赖的bean
首先,在销毁一个 bean 时,如果它有依赖其它的 bean ,则首要目标不是销毁自己,而是先销毁那些依赖的 bean ,所以这里会有递归调用上面 destroySingleton
方法的动作。
自定义bean销毁方法的回调
动作很简单,一个 destroy
方法就完事了,点击去看一下吧:
到这里我们就发现了,它会把我们常用的三种 bean 的销毁手段都依次执行一遍,同时也就解释了 bean 的销毁阶段生命周期回调的顺序:**@PreDestroy**
→ **DisposableBean**
→ **destroy-method**
。
处理bean中bean
这个概念不是很好理解吧,bean 中 bean 是什么鬼?还记得第 9 章中,我们在写复杂类型注入的时候,说可以在 property 中再写 <bean>
这回事吧,不记得也没有关系,我改一下原有的测试代码,想必你一下子就能想起来了吧:
哎,这种嵌套 bean ,就会记录在 DefaultSingletonBeanRegistry
的 containedBeanMap
中,既然外头的 bean 要销毁了,那里头的这些 bean 也就应该被销毁了,所以这里又是取到那些内部构造好的 bean ,依次递归调用 destroySingleton
方法来销毁这些 bean 。
销毁被依赖的bean
注意这里的说法,这里是销毁那些被依赖的 bean ,由于一个 bean 可能被其它 bean 引用或者依赖,所以在最后一步,当自己销毁之后,那些依赖了当前 bean 的 bean 也就应该被销毁。套路都是一样的,取到那些被依赖的 bean 的名称,依次递归调用 destroySingleton
方法销毁。
到这里,destroyBeans
销毁单实例 bean 的逻辑就全部走完了,继续往下看 doClose
方法还干了什么事。
这个步骤,说是关闭 BeanFactory
,实际上更接近于 “销毁” ,因为原来的 BeanFactory
无论如何都无法继续用了。
在基于 xml 和基于注解驱动的两种 ApplicationContext
的实现里,它们的策略既相似又不同:
在基于 xml 配置文件的 ApplicationContext
中,它会获取到原有的 BeanFactory
,移除序列化 ID ,并直接丢弃原来的 BeanFactory
。
在基于注解驱动的 ApplicationContext
中,它只会给内部组合的 BeanFactory
移除序列化 ID 而已。
这个时候可能会有小伙伴们产生疑惑:之前不是说 GenericApplicationContext
不允许重复刷新吗?既然它只是移除了一下序列化 ID ,那刷新的时候重新设置一个不就行了吗?哎,这个时候我们要了解 GenericApplicationContext
中的另外一个成员了:
看这里,如果当前 GenericApplicationContext
是刚创建的,那么 refreshed
的值一定是 false ,此时使用 CAS 设置 true 是成功的,下面的 throw ex
动作不会执行;而第二次再调用 refresh
刷新 ApplicationContext
时,进入到该方法时,CAS 不通过,无法刷新 BeanFactory
,最终抛出异常。
为什么 AbstractRefreshableApplicationContext
就没这事呢?因为 AbstractRefreshableApplicationContext
中本来就没这个 refreshed
的设计,而且人家在关闭 BeanFactory
的时候,直接把原来的 BeanFactory
扔了,不要了,那当然得支持刷新呀(不然 BeanFactory
都没了 ApplicationContext
就是一空壳)。
剩余的动作就不重要了,而且也没什么实在东西,我们就不关注了。
至此,ApplicationContext
关闭,bean 的销毁动作也就全部结束了。
bean 对象在销毁时,由 **ApplicationContext**
发起关闭动作。销毁 bean 的阶段,由 **BeanFactory**
取出所有单实例 bean ,并逐个销毁。
销毁动作会先将当前 bean 依赖的所有 bean 都销毁,随后回调自定义的 bean 的销毁方法,之后如果 bean 中有定义内部 bean 则会一并销毁,最后销毁那些依赖了当前 bean 的 bean 也一起销毁。