上一篇文章我们主要分析了 Bean 创建过程的源码,特别是在 doCreateBean 方法的末尾提到了与 Bean 销毁相关的逻辑代码。这说明在 Bean 的创建阶段就已经与它的销毁机制建立了联系——Spring 会在创建时判断该 Bean 是否需要销毁,并收集对应的销毁方法。当然,此时并不会直接执行销毁逻辑,而是将相关信息记录下来,以便在后续真正需要销毁(例如容器关闭时,对单例 Bean 而言)的时候调用对应的销毁方法。
这一节我们将深入探讨 Bean 销毁的完整过程,大致可以分为两个阶段:
1. Bean 创建过程中的销毁逻辑判断与方法收集
2. 容器关闭时(单例 Bean 场景下)的实际销毁方法调用
本文内容:
Bean销毁过程底层源码解析
DisposableBean接口工作流程底层源码解析
@PreDestroy注解工作流程底层源码解析
inferred机制工作流程底层源码解析
DestructionAwareBeanPostProcessor底层源码解析
适配器设计模式在Bean销毁机制中的应用分析
不同作用域下Bean销毁机制的底层源码解析
Spring容器关闭时触发Bean销毁过程底层源码解析
源码工程改造
为了支持jakarta注解以及后续学习中要引入其他依赖比较mybatis、mysql驱动等,我们必须先解决工程引入依赖的问题。spring-demo.gradle中内容如下:
description = "Spring Demo"
dependencies {
api(project(":spring-core"))
api(project(":spring-beans"))
api(project(":spring-context"))
optional("jakarta.annotation:jakarta.annotation-api:2.0.0")
optional("jakarta.inject:jakarta.inject-api:2.0.1") // 如果需要 JSR-330 注解
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}这一块卡了我两天,原因是之前导入的源码工程自定义模块的配置有些问题,然后我换了vpn端口变了。开始依赖一直导入不进来,也下载不了。我以为是我的IDEA有问题,于是删除了.idea目录,结果整个工程就炸了。现在已经解决,相应的解决方案我已补充到了《Spring6源码编译》的文章中。
@PreDestroy销毁方式收集点源码分析
其实上节在分析初始化前时,分析了有一个BeanPostProcessor是InitDestroyAnnotationBeanPostProcessor,从名称就可以看出这个类与初始化和销毁都有关系。而在它的初始化前方法:
org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization中就会调用到:
org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#buildLifecycleMetadata方法
此方法里面会收集初始化方法和销毁方法,并且是一个do-while循环处理,会处理父类,但要注意最后的顺序问题:
// 初始化方法加最前面,也就是说顶级父类中要是有初始化方法,就最先执行,从上至下
initMethods.addAll(0, currInitMethods);
// 而销毁方法是放最后,是从子及父
destroyMethods.addAll(currDestroyMethods);
// 处理父类,注意外层是一个do-while
currentClass = currentClass.getSuperclass();值得注意:CommonAnnotationBeanPostProcessor是InitDestroyAnnotationBeanPostProcessor的子类
容器启动时会调用org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors,从而会先调用CommonAnnotationBeanPostProcessor的构造方法,将@PostConstruct和@PreDestroy两个注解(其实是4个,因为有jakarta和javax两个包)添加,后面才会调用InitDestroyAnnotationBeanPostProcessor的方法去执行它的初始化前方法,就是上章中分析的:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)方法调用过去的。而org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization方法只会调用Bean的初始化方法,销毁的方法不会在这里调用。
总之,在初始化前这个步骤里面就会与Bean销毁产生关联,因为这个里面收集了Bean销毁的方法,但是不会去执行销毁方法。
其实,这个地方应该是实现JSR-250(二百五规范,哈哈,好记吧)规范的一部分,这个是将@PostConstrut和@PreDestroy方法收集,然后封装成一个org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata对象。在createBean最后还有收集其他销毁方式方法的逻辑。也就是说通过插件InitDestroyAnnotationBeanPostProcessor机制实现了JSR-250规范的初始化和销毁方法。这也体现了一种对称设计吧。
销毁方式的收集点归纳:
InitDestroyAnnotationBeanPostProcessor只负责收@PreDestroy注解方法,其他的销毁方式由不同的机制处理。这种分离设计体现了Spring的职责分离原则,也让扩展更加灵活。
理解Bean销毁方法的调用时机
在阅读doCreateBean最后那段收集其他销毁方式的逻辑代码前,我们先解答下销毁方法调用时机的问题。
当然销毁方法有多种实现,这里我们先选择通过实现DisposableBean接口实现Bean销毁的逻辑:
@Component
public class UserService implements DisposableBean {
public void test() {
System.out.println("xxxx");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy...");
}
}public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
// applicationContext.getBeanFactory().destroyBean(userService);
applicationContext.close();
}
}值得注意的是,在独立应用程序中,如果不手动关闭Spring容器,Bean的销毁方法默认不会被调用。
Spring提供了两种级别的销毁控制:
容器级别:通过
close()或registerShutdownHook()关闭整个容器,自动销毁所有单例BeanBean级别:通过
getBeanFactory().destroyBean()销毁特定的Bean实例,适用于原型Bean或需要手动管理生命周期的场景
registerShutdownHook相当于是给JVM注册一个回调的钩子,最终也是调用doClose方法,区别在于如果JVM进程非正常退出可能会导致无法正常回调。destroyBean方法相当于是手动调用销毁方法。
可以用下面的代码对比close与registerShutdownHook的区别:
public class Main {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
applicationContext.close();
// applicationContext.registerShutdownHook();
System.in.read();
}
}测试registerShutdownHook时,read方法会阻塞当前线程,当输入内容回车后,程序执行结束,JVM进程正常退出才会调用销毁方法。而close方法会直接调用销毁方法。
关于多例Bean的思考
为什么多例Bean,Spring调用不了销毁方法,这里我们简单的理解下:
public class Main {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
// 多例Bean:每次getBean()创建新实例
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
// 关键问题:
// 1. Spring容器没有存储userService实例的引用
// 2. Spring不知道应用程序中持有了多少个userService实例
// 3. close()时只能清理单例池中的Bean,无法清理多例Bean
applicationContext.close();
}
}其实本质上就是说,Spring中没有维护多例Bean的实例和相关的销毁方法的map,后面分析销毁的逻辑就知道了。
前面其实我们在分析:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法时,就知道了,Spring创建多例Bean也是调用org.springframework.beans.factory.support.AbstractBeanFactory#createBean方法,只是不像单例Bean一样创建后会返回放入单例池。
Spring的设计理念是:多例Bean的生命周期管理责任转移给了使用者。
更多关于多例Bean的论述,参考我用AI生成的文章《Spring多例Bean深度解析》,这个文章是AI生成的,可以供参考。
其他销毁方式收集点源码分析
在Bean的创建过程中,于初始化阶段之后,存在一个步骤用于判断当前所创建的Bean是否要注册成为一个disposable的Bean。入口就是上节分析的doCreateBean方法的最后:
/ Register bean as disposable.
// 销毁Bean相关的逻辑
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}registerDisposableBeanIfNecessary方法内,就默认没有处理多例Bean的销毁。只是针对单例Bean和其他作用域进行了不同的判断。我们先来看下外层的org.springframework.beans.factory.support.AbstractBeanFactory#requiresDestruction方法。
判断1:不是NullBean
判断2:org.springframework.beans.factory.support.DisposableBeanAdapter#hasDestroyMethod
org.springframework.beans.factory.support.AbstractBeanFactory#requiresDestruction
org.springframework.beans.factory.support.DisposableBeanAdapter#hasDestroyMethod
第一种情况:判断是否实例了DisposableBean接口。
第二种情况入口:
org.springframework.beans.factory.support.DisposableBeanAdapter#inferDestroyMethodsIfNecessary
此方法它只是返回销毁方法名,用于外面判断是否有销毁方法。此方法中会进行如下判断:
1、是否显式指定过销毁方法,是的话就直接返回方法名数组。
2、缓存的判断,只要有一个销毁方法,就包裹这个方法返回方法名数组。
3、如果没有缓存,则判断 (inferred)情况、实现了AutoCloseable、实现了ExecutorService(JDK19后ExecutorService默认实现实现了AutoCloseable接口,有提供close方法),再设置缓存根据源码分析后,下面我们来分别演示这几种销毁的情况。
显式指定销毁方法
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
// @Override
// public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
// if("userService".equals(beanName)) {
// System.out.println("实例化前");
// // return new User();
// }
// return null;
// }
// 处理合并以后的BeanDefinition,此方法在源码中对于在实例化后初始化之前被调用
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// 这个地方可以修改BeanDefinition的值,影响后续的步骤,前面的步骤是不受影响的,你比如你改beanClass是没有用的,对象都创建了
if ("userService".equals(beanName)) {
// beanDefinition.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
beanDefinition.setDestroyMethodName("b");
}
}
// @Override
// public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
// if("userService".equals(beanName)) {
// System.out.println("实例化后");
// }
// return true;
// }
//
// // 属性处理
// @Override
// public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
// if("userService".equals(beanName)) {
// System.out.println("处理属性");
// // 技术上可以通过MutablePropertyValues实现添加修改属性
// MutablePropertyValues mpvs = new MutablePropertyValues(pvs);
// // 修改已有的属性值
// if (mpvs.contains("password")) {
// // 对密码进行加密
// String rawPassword = (String) mpvs.getPropertyValue("password").getValue();
// // mpvs.add("password", encrypt(rawPassword));
// }
// // 技术上可以:通过 MutablePropertyValues 添加新属性,但不推荐:因为 Bean 可能没有对应的字段/setter
// }
// return pvs;
// }
//
// @Override
// public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// if("userService".equals(beanName)) {
// System.out.println("初始化前");
// }
// return bean;
// }
//
// @Override
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// if("userService".equals(beanName)) {
// System.out.println("初始化后");
// }
// return bean;
// }
}@Component
public class UserService {
public void test() {
System.out.println("xxxx");
}
public void b() {
System.out.println("b....");
}
}public class Main {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
applicationContext.close();
}
}
// 输出
// xxxx
// b....INFER_METHOD
通过源码可以看到,如果BeanDefinition中的destroyMethod方法指定的是(inferred)。并且没有实现DisposableBean接口也没有实现Autocloseable接口就会先尝试收集方法名为close的方法作为销毁方法,如果获取不到就会再尝试shutdown方法。而且要注意的是,这里收集的close或者shutdown方法都是默认不带参数的,带参数的不会收集。
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
// 处理合并以后的BeanDefinition,此方法在源码中对于在实例化后初始化之前被调用
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// 这个地方可以修改BeanDefinition的值,影响后续的步骤,前面的步骤是不受影响的,你比如你改beanClass是没有用的,对象都创建了
if ("userService".equals(beanName)) {
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
}
}
}@Component
public class UserService {
public void test() {
System.out.println("xxxx");
}
// 先收集close方法
// public void close() {
// System.out.println("close....");
// }
// 没有close方法则收集shutdown方法
// public void shutdown() {
// System.out.println("shutdown....");
// }
// 有参数的默认不会收集成销毁方法
public void shutdown(String a) {
System.out.println("shutdown....");
}
}实现Autocloseable
@Component
public class UserService implements AutoCloseable {
public void test() {
System.out.println("xxxx");
}
@Override
public void close() throws Exception {
System.out.println("close....");
}
}警告: [try] 可自动关闭的资源UserService包含的成员方法 close() 可能抛出 InterruptedException
public class UserService implements AutoCloseable {JDK21会报错,因为这个里面可能抛出InterruptedException,所以要不手动处理,这里我简单处理:直接不声明close方法的异常即可。
@Override
public void close() {
System.out.println("close....");
}ExecutorService的情况我这里面就没有演示了,因为我也不会演示,后面再研究吧。
这个就是在判断有没有实现这个接口DestructionAwareBeanPostProcessor的BeanPostProcessor,此接口有两个方法,一个是销毁前方法postProcessBeforeDestruction,一个是是否需要销毁的判断方法requiresDestruction
这个就是在判断有没有实现这个接口DestructionAwareBeanPostProcessor的BeanPostProcessor,此接口有两个方法,一个是销毁前方法postProcessBeforeDestruction,一个是是否需要销毁的判断方法requiresDestruction而其实我们前面看的InitDestroyAnnotationBeanPostProcessor其实就实现了DestructionAwareBeanPostProcessor。我看来看下它是怎么实现的:
org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#requiresDestruction
@Override
public boolean requiresDestruction(Object bean) {
return findLifecycleMetadata(bean.getClass()).hasDestroyMethods();
}其实就是在找,一个Bean上有没有@PreDestroy注解的方法。
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeDestroyMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
String msg = "Destroy method on bean with name '" + beanName + "' threw an exception";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
}
else if (logger.isWarnEnabled()) {
logger.warn(msg + ": " + ex.getTargetException());
}
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
}
}
}postProcessBeforeDestruction会在容器关闭时被调用,里就是真的执行销毁方法了。
而org.springframework.beans.factory.support.DisposableBeanAdapter#hasApplicableProcessors方法,就是循环遍历DestructionAwareBeanPostProcessor需不需要销毁。
至此org.springframework.beans.factory.support.AbstractBeanFactory#requiresDestruction方法分析完成了。回到最外层,如果判断通过,并且是单例Bean,则会执行org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerDisposableBean方法。
此方法就是放入一个disposableBeans的Map中,key是beanName,但是value是一DisposableBeanAdapter对象。这个体现了适配器的设计模式DisposableBeanAdapter类本身自己也实现了DisposableBean接口,在它的destroy方法里面就会去判断各种的销毁情况,并真正的调用。
对于另外作用域的,比如Reqesut、Seesion也是类似收集成了Callback保存,最终应该是从Web服务器调用过程的,比如Tomcat。有时间可以自己搭建一个SpringBoot项目调试下。
上述过程总结:
1. 该Bean是否实现DisposableBean接口。
2. 该Bean是否实现AutoCloseable接口ExecutorService 接口。
3. BeanDefinition中是否定义destroyMethod。
4. 通过调DestructionAwareBeanPostProcessor.requiresDestruction(bean)方法来进一步判断。具体地,
ApplicationListenerDetector中,直接将实现ApplicationListener接口的Bean视为需要进行Bean销毁。InitDestroyAnnotationBeanPostProcessor中,任何带@PreDestroy注解的方法均被视为需要进行Bean销毁。
若上述任一条件成立,则将该Bean适配DisposableBeanAdapter对象,并将其存储disposableBeans集合中,该集合类型LinkedHashMap。
销毁方法分类总结
DK19+还有ExecutorService
真正调用销毁方法源码分析
调用链路:
org.springframework.context.support.AbstractApplicationContext#close
org.springframework.context.support.AbstractApplicationContext#doClose
org.springframework.context.support.AbstractApplicationContext#destroyBeans
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingletons
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroyBean在Spring容器关闭过程中,其执行步骤如下:
1. 首先发ContextClosedEvent事件。
2. 调LifecycleProcessor接口onClose()方法。
3. 销毁单例Bean,具体步骤包括:
遍
disposableBeans集合:
1. 将每DisposableBean从单例池中移除。
2. 调DisposableBeandestroy()方法。
3. 若DisposableBean被其他Bean依赖,则同时销毁这些依赖Bean。
清
manualSingletonNames集合,这是一个存储用户手动注册的单例Bean名称Set。清
allBeanNamesByType映射,这是一Map,键为Bean类型,值为该类型下所有Bean名称的数组。清
singletonBeanNamesByType映射,其结构allBeanNamesByType相似,但仅包含单例Bean。
在这一过程中,涉及到了一种设计模式:适配器模式(Adapter Pattern)。
当Spring容器准备销毁Bean时,它会识别并处理那些实现DisposableBean接口的Bean。然而,在定义一个Bean时,如果该Bean实现DisposableBean接口,或者实现AutoCloseable接口,亦或是在BeanDefinition中指定destroyMethodName,那么这个Bean都将被视为“可销毁的Bean”(Disposable Bean)。因此,在容器关闭时,系统将调用这些Bean对应的销毁方法。
为了实现上述功能,需要引入适配机制,将实现DisposableBean接口AutoCloseable接口或其他方式指定销毁方法的Bean统一适配为实现DisposableBean接口的形式。为此,Spring框架使用DisposableBeanAdapter类。具体而言DisposableBeanAdapter实现DisposableBean接口,在它的
destroy()方法中会去判断到底应该调用Bean的哪个方法进行销毁。
总结
通过本文学习,我们大概知道了Bean销毁的处理过程。其实销毁的流程并不复杂,只是实现的方式有多种。目前我们学习的是SpringFramework,后面的Spring家族其他成员中就会用到这个销毁机制的。
学习到目前为止,我们对于一个Bean的创建和销毁的具体过程已经是了解了的,这个过程非常消耗时间。但是这些都是基本功,是后面学习其他框架的基础。这个过程其实是有意义的,我们不仅仅是学习,更重要的是掌握分析一个复杂框架的思路,一定要坚定信心这个时间花费的绝对功不唐捐!