【spring源码系列】之【Bean的销毁】
以“冬奥之光,多彩冰灯”为主题的第四十一届全国专业冰雕比赛在冰城哈尔滨市进入第二天,60名冰雕高手在哈尔滨冰灯艺术游园会园区展开激烈的竞技比拼。
冰雕艺术
1. 概述
Bean的销毁是Bean的生命周期中最后一步,比如在Tomcat等容器关闭的时候会调用Bean的销毁方法,下面逐步分析。
2. 源码分析
在bean创建完成后,就会对这个bean注册一个销毁的Adapter对象,
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
......
if (instanceWrapper == null) {
//创建对象实例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
......
try {
// 属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 初始化bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
......
// Register bean as disposable.
try {
// 注册销毁的bean
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
registerDisposableBeanIfNecessary
方法中disposableBeans
集合负责收集需要销毁的bean
。
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
if (mbd.isSingleton()) {
// Register a DisposableBean implementation that performs all destruction
// work for the given bean: DestructionAwareBeanPostProcessors,
// DisposableBean interface, custom destroy method.
// 注册销毁bean的DisposableBeanAdapter对象
registerDisposableBean(beanName,
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
}
......
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}
DisposableBeanAdapter
对象就是负责 bean
销毁的类,这个类中收集 bean
是否实现了 DisposableBean
接口
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable
是否配置 destroy-method
属性,过滤了 DestructionAwareBeanPostProcessor
类型的接口,如下图所示:
public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition,
List<BeanPostProcessor> postProcessors, @Nullable AccessControlContext acc) {
......
this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (BeanPostProcessor processor : processors) {
if (processor instanceof DestructionAwareBeanPostProcessor) {
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
if (dabpp.requiresDestruction(bean)) {
filteredPostProcessors.add(dabpp);
}
}
}
}
return filteredPostProcessors;
然后 bean
是在什么时候被销毁呢,在 tomcat
关闭的时候就会调用到 servlet
中的销毁方法,具体是通过类ContextLoaderListener.java
中的contextDestroyed
方法,通过 closeWebApplicationContext
方法一直往下找此为 servlet
规范的使用,一路往下调用。
最终会进入DisposableBeanAdapter
类中的destroy
,方法该方法就会根据前面的收集进行调用。
public void destroy() {
// 处理@PreDestroy注解的beanpostProcessor实现类: InitDestroyAnnotationBeanPostProcessor
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
// 处理实现DisposableBean接口的bean的销毁
if (this.invokeDisposableBean) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
}
try {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((DisposableBean) this.bean).destroy();
return null;
}, this.acc);
}
else {
((DisposableBean) this.bean).destroy();
}
}
catch (Throwable ex) {
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
}
}
}
// 处理在配置文件中的bean配置了destroy-method的bean的销毁
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的顺序是如下:
1)判断是否需要处理@PreDestroy
注解的bean
,如果需要,则通过beanpostProcessor
实现类 InitDestroyAnnotationBeanPostProcessor
处理;
2)判断是否需要处理实现DisposableBean
接口的bean
的销毁;
3)判断是否需要处理配置文件中的bean
配置了destroy-method
的bean
的销毁。
3. 案例演示
定义Bean
,同时加入销毁对应的三种方法;
/**
* @Author: wzj
* @Date: 2021/7/2 11:32
* @Desc:
*/
public class Wzj implements DisposableBean {
public static Wzj factoryMethod() {
CQ cq = new CQ();
SC sc = new SC();
return new Wzj(sc, cq);
}
@PreDestroy
public void close() {
System.out.println("通过 @PreDestroy:销毁实例wzj");
}
public void destroyMethod() {
System.out.println("通过配置文件配置destroy-method:销毁实例wzj");
}
@Override
public void destroy() {
System.out.println("通过DisposableBean接口:销毁实例wzj");
}
配置文件如下:
<bean id="wzj" class="com.wzj.bean.Wzj" factory-method="factoryMethod" destroy-method="destroyMethod"/>
测试类:
/**
* @Author: wzj
* @Date: 2021/3/30 15:08
* @Desc: 测试类
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring.xml"})
public class TestSpring {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testDestroy() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Wzj wzj = (Wzj)applicationContext.getBean("wzj");
applicationContext.getBeanFactory().destroyBean("wzj");
}
结果:
有人可能会问,为何Bean
可以多次销毁,其实Bean
的销毁并不是真正意义上的销毁Bean
,而是在销毁前执行销毁方法,可能包含关闭数据库连接、关闭网络请求等逻辑操作,而后真正的销毁是由Spring
容器执行关闭,其内部Bean
也就自然而然消失了,Bean
销毁是发生在Spring
容器关闭过程中的。
【spring源码系列】之【Bean的销毁】的更多相关文章
- Spring源码系列(二)--bean组件的源码分析
简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...
- Spring源码系列(三)--spring-aop的基础组件、架构和使用
简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...
- Spring源码系列(四)--spring-aop是如何设计的
简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...
- Spring源码系列 — Bean生命周期
前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...
- Spring源码系列 — 注解原理
前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...
- Spring源码系列 — BeanDefinition
一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- 事件机制-Spring 源码系列(4)
事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...
- Ioc容器依赖注入-Spring 源码系列(2)
Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...
- Ioc容器BeanPostProcessor-Spring 源码系列(3)
Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...
随机推荐
- 如何保存并复制python虚拟环境
关于虚拟环境的一些基础概念学习了本期视频 保存 以我的一个虚拟环境示例: 在要保存的虚拟环境下使用: pip freeze > requirements.txt 复制 pip install - ...
- Java枚举类在生产环境中的使用方式
前言 Java枚举在项目中使用非常普遍,许多人在做项目时,一定会遇到要维护某些业务场景状态的时候,往往会定义一个常量类,然后添加业务场景相关的状态常量.但实际上,生产环境的项目中业务状态的定义大部 ...
- 从服务端生成Excel电子表格(GcExcel + SpreadJS)
在服务端生成Excel电子表格,除了使用 Node.js + SpreadJS 外,葡萄城官方推荐使用 SpreadJS + GcExcel.该方案不仅能够解决批量绑定数据源并导出Excel.批量修改 ...
- [NOI Online #1 提高组]
A 首先从 \(t = 2\) 的特殊部分分出发. 不难发现这个操作是很不直观的,于是可以考虑对于每个操作 \((u, v)\) 在 \(u, v\) 之间连一条无向边. 显然连通块之间要分开考虑,对 ...
- SP19149 INS14H - Virus Revisited
可以发现,如果一个整体一起考虑是不能找到一个合适的状态来描述这个情形的. 因此可以考虑寻找整体的反面,也就是将每个维度分开考虑. 不难发现每个维度本质上是一样的,因此不需要考虑不同维度之间的区别. 那 ...
- Atcoder ARC-068
A 不难发现从 \(5\) 开始一直往 \(6\) 转再转回来是最优的,直接模拟即可. B 不难发现可以将多余部分直接贪心消去,最后必然会剩下两个或 \(1\) 个多余的数. 如果剩下两个,此时多余的 ...
- Sleep_Yield_Join
名称解释 Sleep:意思就是睡眠,当前线程暂停一段时间让给别的线程去运行;Sleep是怎么复活的?由你的睡眠时间而定,等睡眠到规定的时间自动复活. Yield:就是当前线程正在执行的时候停止下来进入 ...
- Android 四种方法写按钮点击事件
1.匿名内部类的方式 2. 创建一个类实现onclickListener,实现onclick方法,设置控件点击事件时传一个类的对象. 3. 让当前类实现onclickListener,设置控件点击事件 ...
- VC 获取当前运行窗口名称
转载请注明来源:https://www.cnblogs.com/hookjc/ BOOL CALLBACK WindowChild(HWND hwnd,LPARAM lparam){ CFGDlg* ...
- Ubuntu20.04 安装MongoDB及其基本使用
MongoDB 官方文档 本博客旨在自我学习使用,如有任何疑问请及时联系博主 基本介绍 MongoDB 是由C++语言编写并基于分布式文件存储的开源数据库. MongoDB 是一款介于关系数据库和非关 ...