【Spring源码系列】Bean生命周期-Bean销毁_spring之销毁bean流程-程序员宅基地

技术标签: spring  java  

前言

Spring给我们提供了一种当bean销毁时调用某个方法的方式。那么,Spring底层到底是如何实现的呢?接下来,我们将从源码+案例的方式来解析:spring如何实现当bean销毁时调用某个方法的。

一、Bean销毁介绍

bean销毁的时机

spring容器关闭的时候(调用close())方法的时候,所有的单例bean都会被销毁,并且对于实现destroy方法的bean,也会在此刻执行各自自定义的销毁逻辑

提示:
是spring容器关闭的时候调用bean销毁逻辑,不是垃圾回收、程序意外终止、程序正常终止…的时候。

spring注册DestroyBean时机

1、注册DisposableBeans。在‘初始化后’会对BeanDefinition进行判断,判断该BeanDefinition是否具备destroy方法,如果具备则把BeanDefinition注册到DisposableBeans。具体如何判断的,我们下面会讲;
在这里插入图片描述
2、执行destroy方法。当调用close方法的时候,会遍历DisposableBeans执行每一个销毁方法

定义bean销毁方式以及源码调试

此处不仅仅写了代码示例,也把源码贴出来进行验证。

使用@PreDestroy注解

代码示例:

@Component
public class UserService {
    

	@Autowired
	private OrderService orderService;

	public void test(){
    
		System.out.println(orderService);
	}

	@PreDestroy
	public void myDestroyUserServiceMethod () {
    
		System.out.println("UserService#myDestroyUserServiceMethod");
	}
}

源码:

	protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
    
		// hasDestroyMethod: 实现了DisposableBean或者AutoCloseable接口	,或者创建bean的时候手动指定了销毁方法( 比如@Bean(destroyMethod = "destory")、xml中的bean标签中指定destroyMethod)
		return (bean.getClass() != NullBean.class && (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) ||
				// @PreDestroy注解
				(hasDestructionAwareBeanPostProcessors() && DisposableBeanAdapter.hasApplicableProcessors(
						bean, getBeanPostProcessorCache().destructionAware))));
	}

代码调试:
CommonAnnotationBeanPostProcessor:
在这里插入图片描述UserService#myDestroyUserServiceMethod销毁方法在Spring容器启动的时候就已经被记录在CommonAnnotationBeanPostProcessor中了,当调用org.springframework.beans.factory.support.AbstractBeanFactory#requiresDestruction判断该bean是否定义销毁逻辑的时候返回的是true:
在这里插入图片描述

实现DisposableBean或者AutoCloseable接口

代码示例:

@Component
public class UserService implements DisposableBean {
    

	@Autowired
	private OrderService orderService;

	public void test(){
    
		System.out.println(orderService);
	}

	@Override
	public void destroy () {
    
		System.out.println("UserService#destroy");
	}
}

源码:

	public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
    
		// 是否实现了这两个接口中的一个
		if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
    
			return true;
		}
		// 判断BeanDefinition是否指定了销毁方法
		return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
	}

源码调试:
UserService 实现了 DisposableBean 接口,所以DisposableBeanAdapter.hasDestroyMethod(bean, mbd)返回true,且可以发现CommonAnnotationBeanPostProcessor#lifecycleMetadataCache集合中的UserService.class并没指定destroyMethods:
在这里插入图片描述

手动指定destroy方法(@Bean、XML)

手动指定dstroy方法有两种方式:
1、@Bean注解方式指定destroyMethod;
2、XML文件中< bean >标签里面指定destry-method;

public class OrderService {
    

	public void destroy () {
    
		System.out.println("OrderService#destroy");
	}
}
@ComponentScan("com.cms")
public class AppConfig {
    

	@Bean(destroyMethod = "destroy")
	public OrderService createOrderService () {
    
		return new OrderService();
	}
	
}

在这里插入图片描述

手动指定destroy方法((inferred))

代码示例:

public class OrderService {
    

  // 必须是close方法
	public void close () {
    
		System.out.println("OrderService#destroy");
	}
}
@ComponentScan("com.cms")
public class AppConfig {
    

	@Bean(destroyMethod = "(inferred)")
	public OrderService createOrderService () {
    
		return new OrderService();
	}
	
}

源码:

	@Nullable
	private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
    
		// 判断BeanDefinition是否指定了销毁方法(比如创建bean的时候(@Bean、xml),手动指定destroyMethod)
		String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
		// 下面这种定义销毁的方式,不常用。流程:先定义销毁方法-(inferred) ,然后调用close方法。
		if (destroyMethodName == null) {
    
			destroyMethodName = beanDefinition.getDestroyMethodName(); //
			if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
					(destroyMethodName == null && bean instanceof AutoCloseable)) {
    
				// Only perform destroy method inference or Closeable detection
				// in case of the bean not explicitly implementing DisposableBean
				destroyMethodName = null;
				if (!(bean instanceof DisposableBean)) {
    
					try {
    
						destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
					}
					catch (NoSuchMethodException ex) {
    
						try {
    
							destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
						}
						catch (NoSuchMethodException ex2) {
    
							// no candidate destroy method found
						}
					}
				}
			}
			beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
		}
		return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
	}

手动指定destroy方法(MergedBeanDefinitionPostProcessor后置处理器设置销毁方法)

@Component
public class MyMergeBdfPostProcesser implements MergedBeanDefinitionPostProcessor {
    
	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    
		if (beanName.equals("myDisposableBean3")){
    
			beanDefinition.setDestroyMethodName("a");
		}
	}
}
 
 
@Component
public class MyDisposableBean3 {
    
 
	public void a()  {
    
		System.out.println("MyMergeBdfPostProcesser-后置处理器销毁");
	}
}

或者

@Component
public class MyMergeBdfPostProcesser2 implements MergedBeanDefinitionPostProcessor {
    
	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    
		if (beanName.equals("myDisposableBean4")){
    
			beanDefinition.setDestroyMethodName("(inferred)");
		}
	}
}
 
 
 
@Component
public class MyDisposableBean4 {
    
 
//	public void close()  {
    
//		System.out.println("close销毁");
//	}
 
 
	public void shutdown()  {
    
		System.out.println("shutdown销毁");
	}
}

二、Bean销毁-源码分析

声明关键点

1、原型bean即使定义了销毁方法,也不会执行销毁方法。因为我们的原型bean根本没有存,更不要说去调用原型bean的销毁方法了。

源代码

注册

源码位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// 只做一件事情:注册实现了'销毁'方法的bean。
registerDisposableBeanIfNecessary(beanName, bean, mbd);
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
    
		AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
		// if(不是'多例'bean && 有销毁方法)
		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.
				registerDisposableBean(beanName, new DisposableBeanAdapter(
						bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
			}
			else {
    
				// A bean with a custom scope...
				Scope scope = this.scopes.get(mbd.getScope());
				if (scope == null) {
    
					throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
				}
				scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(
						bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
			}
		}
	}

销毁

源码位置:org.springframework.context.support.AbstractApplicationContext#close

	protected void destroyBeans() {
    
		// 只对单例bean存储销毁方法,原型bean不会存储(因为原型bean每次调用都会创建新bean对象)
		// DefaultListableBeanFactory
		getBeanFactory().destroySingletons();
	}

在Spring容器关闭过程时:

  1. 首先发布ContextClosedEvent事件
  2. 调用lifecycleProcessor的onCloese()方法
  3. 销毁单例Bean
    a. 遍历disposableBeans
    ⅰ. 把每个disposableBean从单例池中移除
    ⅱ. 调用disposableBean的destroy()
    ⅲ. 如果这个disposableBean还被其他Bean依赖了,那么也得销毁其他Bean
    ⅳ. 如果这个disposableBean还包含了inner beans,将这些Bean从单例池中移除掉 (inner bean参考https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-inner-beans)
    b. 清空manualSingletonNames,是一个Set,存的是用户手动注册的单例Bean的beanName
    c. 清空allBeanNamesByType,是一个Map,key是bean类型,value是该类型所有的beanName数组
    d. 清空singletonBeanNamesByType,和allBeanNamesByType类似,只不过只存了单例Bean
    这里涉及到一个设计模式:适配器模式
    在销毁时,Spring会找出实现了DisposableBean接口的Bean。
    但是我们在定义一个Bean时,如果这个Bean实现了DisposableBean接口,或者实现了AutoCloseable接口,或者在BeanDefinition中指定了destroyMethodName,那么这个Bean都属于“DisposableBean”,这些Bean在容器关闭时都要调用相应的销毁方法。
    所以,这里就需要进行适配,将实现了DisposableBean接口、或者AutoCloseable接口等适配成实现了DisposableBean接口,所以就用到了DisposableBeanAdapter。
    会把实现了AutoCloseable接口的类封装成DisposableBeanAdapter,而DisposableBeanAdapter实现了DisposableBean接口。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_43783527/article/details/128172577

智能推荐

如何让文字位于图片的右边并垂直居中?_文字在图片右边高度居中-程序员宅基地

文章浏览阅读3.5k次。如何让文字位于图片的右边并垂直居中?首先要知道图片的高度图中的头像height为40px_文字在图片右边高度居中

PWN 栈溢出-程序员宅基地

文章浏览阅读937次。Beginning如果想用栈溢出来执行攻击指令,就要在溢出数据内包含攻击指令的内容或地址,并且要将程序控制权交给该指令。攻击指令可以是自定义的指令片段,也可以利用系统内已有的函数及指令0x01函数调用栈是指程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(ca..._pwn 栈溢出

文件下载时文件后缀与contentType对应表_application/x-msdownload对应的文件类型-程序员宅基地

文章浏览阅读6.4k次,点赞2次,收藏9次。文件类型如下:".*"="application/octet-stream"".001"="application/x-001"".301"="application/x-301"".323"="text/h323"".906"="application/x-906"".907"="drawing/907"".a11"="application/x-a11""._application/x-msdownload对应的文件类型

系统更新及疑难处理-程序员宅基地

文章浏览阅读345次。系统更新及疑难处理利用WSUS部署更新程序微软公司的主要补丁类型nHotfix是针对某一个具体的系统漏洞或安全问题而发布的专门解决该漏洞或安全问题的小程序,通常称为修补程序n微软公司会及时地将软件产品中发现的重大问题以安全公告的形式公布于众,这些公告都有一个惟一的编号,即“MS”,如MS04-011n还有一种形式为KB(2003年4月份后用此编号)的编号,这个编..._允许来自internetmicrosoft更新服务位置的签名更新

【论文】ROS系统的无人小车自动跟随方案研究-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏45次。本文基于ROS操作系统介绍了一种运动与跟随系统的设计及实现,该跟随系统包括以激光雷达进行全方位角度的距离测量,使用Python语言编写脚本,通过编写激光雷达话题的订阅以及运动话题的发布,完成对目标的运动控制以及对被跟随目标的感知和产生跟随相应的动作。本系统中无论被跟随目标位于跟随机器人的任何方位,均能实现机器人的跟随,无需额外的基站、标签等定位设施,可以减少整个跟随系统成本。基于ROS系统使开发简单快速,适用范围广,具有良好的可移植性和通用性。_ros系统的无人小车自动跟随方案研究

几十款游戏的简单分析_游戏分析-程序员宅基地

文章浏览阅读1.3k次。笔者曾在多个游戏平台上玩过众多游戏,包括fc红白游戏机游戏、网页游戏、单机游戏、客户端游戏、手游等。在游戏过程中,笔者常常从多个角度对游戏进行分析,包括游戏的优缺点、改进方法、数值设计、音效、画面等方面。本篇文章简要但关键地记录了笔者在游戏体验和策略分析方面的一些心得体会。综合以上所述,一款成功的游戏需要满足一些基本要素。首先,它必须是新颖的、有趣的、易于上手的,同时游戏数值、画面、音效等方面也要在合理区间。_游戏分析

随便推点

H3C模拟器配置vlan-程序员宅基地

文章浏览阅读3.3k次,点赞4次,收藏7次。Valn 11组网需求• 交换机GE_2上的VLAN 5 和VLAN 10 为Primary VLAN,其上层端口GigabitEthernet1/0/1需要允许VLAN 5 和VLAN 10 的报文携带VLAN Tag 通过。• 交换机GE_2 的下行端口GigabitEthernet1/0/2 允许Secondary VLAN 2 通过,GigabitEthernet1/0/3 允许Sec..._新华3模拟器vlan配置

img撑满全屏的方法(img非背景图)_img 铺满-程序员宅基地

文章浏览阅读4.7w次,点赞8次,收藏15次。我有一个模板,想按常规做一个div里面放置一个img图片,并且让图片铺满容器,自适应容器大小。HTML结构代码如下(在这个盒模型上,我已经放置了一些不重要的样式)。div style="height:270px;width:400px;border:2px black solid;"> a href="http://www.paipk.com">img src="..." alt="拍_img 铺满

UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xb0 in position 5: invalid start byte_ebpf bcc unicodedecodeerror: 'utf-8' codec can't d-程序员宅基地

文章浏览阅读947次。UnicodeDecodeError: 'gbk' codec can't decode byte 0xfa in position 4669: illegal multibyte sequenceUnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 5: invalid start bytewith open('进线汇总20201211.csv',encoding='utf8') as f: t = f._ebpf bcc unicodedecodeerror: 'utf-8' codec can't decode byte 0xb0 in positio

使用条件序列GAN改进NMT_improving neural machine translation with conditio-程序员宅基地

文章浏览阅读1.3k次。使用条件序列GAN改进NMT原文《Improving Neural Machine Translation with Conditional Sequence Generative Adversarial Nets》课程作业,因为要导出pdf所以粘贴到CSDN了,34章是笔者翻译的部分。当一篇post吧,求别喷,有问题请留言我一定改,一定改。摘要本文提出了一种将GANs应用于NMT领域的方..._improving neural machine translation with conditional sequence generative ad

产品周报第33期|完善铁粉规则,优化原创保护策略,升级创作中心的数据展示,开放业界专家自定义域名权益……_创作者中心铁粉数0-程序员宅基地

文章浏览阅读5k次。目录一、博客产品功能完善1、完善铁粉说明规则2、创作中心专栏数据升级3、发文助手新增「添加模版」指引4、免费开放业界专家自定义域名权益5、其他优化二、问答产品体验优化1、回答链接和链接详情页调整2、PC端提问页优化3、创作中心页面的问答列表优化三、首页热榜及优质内容推进方面的改进四、吐槽提建议直通车,直达CSDN各产品与运营人员查看往期改进hello,大家好,这里是「CSDN产品周报」第33期。本次更新主要涉及博客、问答及首页,欢迎大家详细了解和使_创作者中心铁粉数0

自建网盘之 NextCloud 终极记录-程序员宅基地

文章浏览阅读1.7k次。自建过许多网盘,试过 可道云、Seafile、FileRun、Nextcloud,但Nextcloud的如下特性吸引了我:完整、好用的客户端,包括 windows、mac、android、ios ...强大的插件扩展,如 Talk, Contacts, notes, Maps ...完整的第三方扩展,支持 Amazie S3, OneDrive, ..._可道云 nextcloud seafile