如何获取Spring关闭钩子中单例Bean的销毁顺序以排查依赖问题?
一、常规手段获取Bean销毁顺序
用
DestructionAwareBeanPostProcessor记录销毁顺序
自定义一个实现DestructionAwareBeanPostProcessor的组件,在postProcessBeforeDestruction方法里打印或记录Bean名称——这个方法会在Bean销毁前被调用,能精准捕获实际销毁顺序。
示例代码:import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.stereotype.Component; @Component public class BeanDestructionLogger implements DestructionAwareBeanPostProcessor { @Override public void postProcessBeforeDestruction(Object bean, String beanName) throws Exception { System.out.println("销毁Bean: " + beanName); // 也可以把beanName写入日志文件或集合,方便后续分析 } }启动容器后,关闭时就能看到完整的销毁顺序日志。
对比注册顺序排查异常
通过ConfigurableApplicationContext拿到DefaultListableBeanFactory,调用getSingletonNames()获取Bean的注册顺序。Spring默认的销毁顺序和注册顺序相反,但如果有依赖关系,依赖方会先于被依赖方销毁。如果某Bean的销毁顺序不符合反向注册顺序,基本可以确定存在隐藏依赖。
二、Spring关闭钩子中的销毁顺序查询
Spring关闭钩子本质是触发ApplicationContext.close(),最终执行DefaultListableBeanFactory.destroySingletons()。要在这个阶段追踪顺序,试试这两种方法:
用
@PreDestroy或DisposableBean记录销毁事件
给Bean加上@PreDestroy注解,或者让Bean实现DisposableBean接口,在对应的销毁方法里打日志。这些逻辑会在关闭钩子执行过程中按顺序触发,直接通过日志就能看到销毁顺序。
示例:import org.springframework.stereotype.Component; @Component public class SampleBean implements DisposableBean { @Override public void destroy() throws Exception { System.out.println("SampleBean 开始销毁"); } }监听
ContextClosedEvent扩展关闭逻辑
自定义ApplicationListener<ContextClosedEvent>,在容器关闭事件触发时(也就是关闭钩子执行阶段),遍历容器中的单例Bean列表,结合之前的销毁日志对比顺序差异。
示例:import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @Component public class ContextCloseListener implements ApplicationListener<ContextClosedEvent> { @Override public void onApplicationEvent(ContextClosedEvent event) { ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) event.getApplicationContext().getBeanFactory(); String[] singletonNames = beanFactory.getSingletonNames(); System.out.println("容器关闭时的单例Bean注册顺序:"); for (String name : singletonNames) { System.out.println(name); } // 结合之前的销毁日志,对比注册顺序和销毁顺序的差异,定位异常 } }
三、通过销毁顺序排查间接依赖
- 如果发现Bean A销毁顺序晚于Bean B,但A没有显式依赖B,大概率存在间接依赖:比如A依赖的Bean C依赖B,或者A通过
ApplicationContext.getBean()等方式隐式获取了B。 - 可以用
BeanFactoryUtils.beansOfTypeIncludingAncestors()方法遍历Bean的依赖关系,结合销毁顺序异常的Bean,顺藤摸瓜找出隐藏的依赖链。
内容的提问来源于stack exchange,提问作者CS1999

