Spring IOC原理-Bean的生命周期-Bean的销毁阶段

从 0 开始深入学习 Spring 专栏目录总览

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

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

  • bean 销毁前 IOC 容器的处理
  • bean 销毁阶段的回调

在测试代码中,我们有主动调用 ApplicationContext 的 stop 和 close 方法,这样 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

public void close() {
    synchronized (this.startupShutdownMonitor) {
        doClose();
        if (this.shutdownHook != null) {
            try {
                Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
            }
            // catch ......
        }
    }
}

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

2.1 广播事件

protected void doClose() {
    // Check whether an actual close attempt is necessary...
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
        // logger ......
        LiveBeansView.unregisterApplicationContext(this);
        try {
            // Publish shutdown event.
            publishEvent(new ContextClosedEvent(this));
        } // catch ......
        // ......

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

2.2 LifecycleProcessor#onClose

        // ......
        // Stop all Lifecycle beans, to avoid delays during individual destruction.
        if (this.lifecycleProcessor != null) {
            try {
                this.lifecycleProcessor.onClose();
            } // catch ......
        }
        // ......

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

public void onClose() {
    stopBeans();
    this.running = false;
}

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

2.3 destroyBeans – 销毁bean

        // ......
        // Destroy all cached singletons in the context's BeanFactory.
        destroyBeans();
        // ......

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

protected void destroyBeans() {
    getBeanFactory().destroySingletons();
}

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

2.3.1 DefaultListableBeanFactory#destroySingletons

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

public void destroySingletons() {
    super.destroySingletons();
    // 清空单实例bean的名称
    updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
    clearByTypeCache();
}

private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);
private void clearByTypeCache() {
    this.allBeanNamesByType.clear();
    this.singletonBeanNamesByType.clear();
}

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

2.3.2 DefaultSingletonBeanRegistry#destroySingletons

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

public void destroySingletons() {
    // logger ......
    synchronized (this.singletonObjects) {
        this.singletonsCurrentlyInDestruction = true;
    }

    String[] disposableBeanNames;
    synchronized (this.disposableBeans) {
        // set -> array
        disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
    }
    // 销毁所有单实例bean
    for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
        destroySingleton(disposableBeanNames[i]);
    }

    // 清空一切缓存
    this.containedBeanMap.clear();
    this.dependentBeanMap.clear();
    this.dependenciesForBeanMap.clear();

    clearSingletonCache();
}

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

2.3.3 destroySingleton

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

public void destroySingleton(String beanName) {
    super.destroySingleton(beanName);
    removeManualSingletonName(beanName);
    clearByTypeCache();
}

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

public void destroySingleton(String beanName) {
    // Remove a registered singleton of the given name, if any.
    // 此处会清理掉BeanFactory中设计的用于处理bean循环依赖的三级缓存
    // 如果小伙伴对此感兴趣,可以参考boot小册第15章
    removeSingleton(beanName);

    // Destroy the corresponding DisposableBean instance.
    DisposableBean disposableBean;
    synchronized (this.disposableBeans) {
        disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
    }
    destroyBean(beanName, disposableBean);
}

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

2.3.4 destroyBean

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

2.3.4.1 销毁依赖的bean
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
    Set<String> dependencies;
    synchronized (this.dependentBeanMap) {
        dependencies = this.dependentBeanMap.remove(beanName);
    }
    if (dependencies != null) {
        // logger ......
        for (String dependentBeanName : dependencies) {
            destroySingleton(dependentBeanName);
        }
    }
    // ......

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

2.3.4.2 自定义bean销毁方法的回调
    // ......
    // Actually destroy the bean now...
    if (bean != null) {
        try {
            bean.destroy();
        }
        // catch ......
    }
    // ......

动作很简单,一个 destroy 方法就完事了,点击去看一下吧:(注意看源码中的注释)

public void destroy() {
    if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
        // 回调DestructionAwareBeanPostProcessor
        // 此处会有执行@PreDestroy注解标注的销毁方法
        for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
            processor.postProcessBeforeDestruction(this.bean, this.beanName);
        }
    }

    if (this.invokeDisposableBean) {
        // logger ......
        try {
            // ......
            else {
                // 回调DisposableBean接口的destroy方法
                ((DisposableBean) this.bean).destroy();
            }
        }
        // catch ......
    }

    // 回调自定义的destroy-method方法
    if (this.destroyMethod != null) {
        invokeCustomDestroyMethod(this.destroyMethod);
    }
    else if (this.destroyMethodName != null) {
        Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
        if (methodToInvoke != null) {
            invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
        }
    }
}

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

2.3.4.3 处理bean中bean
    // ......
    // Trigger destruction of contained beans...
    Set<String> containedBeans;
    synchronized (this.containedBeanMap) {
        // Within full synchronization in order to guarantee a disconnected Set
        containedBeans = this.containedBeanMap.remove(beanName);
    }
    if (containedBeans != null) {
        for (String containedBeanName : containedBeans) {
            destroySingleton(containedBeanName);
        }
    }
    // ......

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

<bean id="cat" class="com.linkedbear.spring.lifecycle.e_source.bean.Cat">
    <property name="name" value="mimi"/>
    <property name="master">
        <bean class="com.linkedbear.spring.lifecycle.e_source.bean.Person"/>
    </property>
</bean>

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

2.3.4.4 销毁被依赖的bean
    // ......
    // Remove destroyed bean from other beans' dependencies.
    synchronized (this.dependentBeanMap) {
        for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {
            Map.Entry<String, Set<String>> entry = it.next();
            Set<String> dependenciesToClean = entry.getValue();
            dependenciesToClean.remove(beanName);
            if (dependenciesToClean.isEmpty()) {
                it.remove();
            }
        }
    }

    // Remove destroyed bean's prepared dependency information.
    this.dependenciesForBeanMap.remove(beanName);
}

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

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

2.4 关闭BeanFactory

    // Close the state of this context itself.
    closeBeanFactory();

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

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

// AbstractRefreshableApplicationContext
protected final void closeBeanFactory() {
    DefaultListableBeanFactory beanFactory = this.beanFactory;
    if (beanFactory != null) {
        beanFactory.setSerializationId(null);
        this.beanFactory = null;
    }
}

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

// GenericApplicationContext
protected final void closeBeanFactory() {
    this.beanFactory.setSerializationId(null);
}

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

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

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

private final AtomicBoolean refreshed = new AtomicBoolean();

protected final void refreshBeanFactory() throws IllegalStateException {
    if (!this.refreshed.compareAndSet(false, true)) {
        throw new IllegalStateException(
            "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
    }

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

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

2.5 剩余动作

    // ......
    // Let subclasses do some final clean-up if they wish...
    // 给子类用的,然而子类没一个重写的
    onClose();

    // Reset local application listeners to pre-refresh state.
    if (this.earlyApplicationListeners != null) {
        this.applicationListeners.clear();
        this.applicationListeners.addAll(this.earlyApplicationListeners);
    }

    // Switch to inactive.
    this.active.set(false);
}

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

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

面试如何回答bean销毁阶段的生命周期

以下答案仅供参考,可根据个人理解进行实际调整:

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

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

【好了,bean 的完整生命周期终于都看完了,IOC 原理部分的最后一章,我们来对整个 ApplicationContext 的生命周期有一个大致的了解,做到能从整体把控好 ApplicationContext 在初始化阶段做的工作】

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring IOC原理-Bean的生命周期-Bean的销毁阶段