hexon
发布于 2026-01-12 / 6 阅读
0

10、Bean销毁过程生命周期源码解析

上一篇文章我们主要分析了 Bean 创建过程的源码,特别是在 doCreateBean 方法的末尾提到了与 Bean 销毁相关的逻辑代码。这说明在 Bean 的创建阶段就已经与它的销毁机制建立了联系——Spring 会在创建时判断该 Bean 是否需要销毁,并收集对应的销毁方法。当然,此时并不会直接执行销毁逻辑,而是将相关信息记录下来,以便在后续真正需要销毁(例如容器关闭时,对单例 Bean 而言)的时候调用对应的销毁方法。

这一节我们将深入探讨 Bean 销毁的完整过程,大致可以分为两个阶段:

1. Bean 创建过程中的销毁逻辑判断与方法收集

2. 容器关闭时(单例 Bean 场景下)的实际销毁方法调用

本文内容:

  1. Bean销毁过程底层源码解析

  2. DisposableBean接口工作流程底层源码解析

  3. @PreDestroy注解工作流程底层源码解析

  4. inferred机制工作流程底层源码解析

  5. DestructionAwareBeanPostProcessor底层源码解析

  6. 适配器设计模式在Bean销毁机制中的应用分析

  7. 不同作用域下Bean销毁机制的底层源码解析

  8. 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规范的初始化和销毁方法。这也体现了一种对称设计吧。

销毁方式的收集点归纳:

销毁方式

收集位置

执行位置

@PreDestroy

InitDestroyAnnotationBeanPostProcessor.buildLifecycleMetadata()

DisposableBeanAdapter.invokeCustomDestroyMethod()

DisposableBean接口

AbstractAutowireCapableBeanFactory.registerDisposableBeanIfNecessary()

DisposableBeanAdapter.destroy()

destroy-method(XML)

AbstractAutowireCapableBeanFactory.registerDisposableBeanIfNecessary()

DisposableBeanAdapter.invokeCustomDestroyMethod()

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提供了两种级别的销毁控制:

  1. 容器级别:通过close()registerShutdownHook()关闭整个容器,自动销毁所有单例Bean

  2. Bean级别:通过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

销毁方法分类总结

类型

标识方式

标准

Spring 处理

DisposableBean

implements DisposableBean

Spring

直接调用接口方法

@PreDestroy

方法注解

JSR-250

BeanPostProcessor

destroy-method

XML/注解配置

Spring

反射调用指定方法

AutoCloseable

implements AutoCloseable

Java 7

推断使用 close()

Inferred

destroyMethod="(inferred)"

Spring

自动推断最佳方法

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的创建和销毁的具体过程已经是了解了的,这个过程非常消耗时间。但是这些都是基本功,是后面学习其他框架的基础。这个过程其实是有意义的,我们不仅仅是学习,更重要的是掌握分析一个复杂框架的思路,一定要坚定信心这个时间花费的绝对功不唐捐!