Springboot扩展点之DisposableBean
原文 by :凡夫编程
前言
DisposableBean,是在Spring容器关闭的时候预留的一个扩展点,从业务开发的角度来看,基本上是用不到的,但是Spring容器从启动到关闭,是Spring Bean生命周期里一个绕不开的节点,因此还是有必要学习一下,以便对Spring能有一个更加全面的认识。
功能特性
1、DisposableBean是一个接口,为Spring bean提供了一种释放资源的方式 ,只有一个扩展方法destroy();
2、实现DisposableBean接口,并重写destroy(),可以在Spring容器销毁bean的时候获得一次回调;
3、destroy()的回调执行时机是Spring容器关闭,需要销毁所有的bean时;
实现方式
与InitializingBean比较类似的是,InitializingBean#afterPropertiesSet()是在bean初始化的时候触发执行,DisposableBean#destroy()是在bean被销毁的时候触发执行,这里结合Springboot扩展点之InitializingBean,用一个示例分析一下DisposableBean扩展接口的相关特性:
1、定义Dog类,实现InitializingBean、DisposableBean接口,并重写afterPropertiesSet()、destroy()
@Slf4j
public class Dog implements InitializingBean, DisposableBean {
private String name = "wang cai";
private Food food;
public Dog() {
log.info("----Dog的无参构造方法被执行");
}
@Autowired
public void setFood(Food food) {
this.food = food;
log.info("----dog的food属性被注入");
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("----com.fanfu.entity.Dog.afterPropertiesSet触发执行");
}
public void myInitMethod() {
log.info("----com.fanfu.entity.Dog.myInitMethod触发执行");
}
@Override
public void destroy() throws Exception {
log.info("----com.fanfu.entity.Dog.destroy触发执行");
}
}
2、单元测试也比较简单,先启动Spring容器,然后再优雅地关闭;
@Test
public void test5(){
log.info("----单元测试执行开始");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
log.info("----开始关闭Spring容器");
context.registerShutdownHook();
log.info("----Spring容器已经关闭完成");
log.info("----单元测试执行完毕");
}
单元测试执行结果:
从单元测试的执行结果来看,Spring容器关闭后,会触发执行DisposableBean#destroy()扩展方法的执行,所以如果我们的业务开发中,如果某些Bean在容器关闭后,需要做一些释放业务资源之类的操作,就能用到这个扩展点了。有的小伙伴也许会有疑问:上面为什么单元测试执行完了,才触发Dog.destroy()方法执行的?其实是这样的,你仔细观察会发现,触发Dog.destroy()方法执行并不是主线程,而是叫做SpringContextShutdownHook的线程,这里用到了多线程技术,单元测试执行完了,才触发Dog.destroy()方法执行是多线程异步执行的原因。
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// 多线程执行容器关闭的操作,主要逻辑在doClose()
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
工作原理
从实现方式示例中,可以了解Spring容器关闭时,使用了多线程技术调用了doClose()来完成相关操作,然后触发了DisposableBean#destroy()扩展方法的执行。
doClose()中的逻辑也相对简单,先发布一个ContextClosedEvent事件,告诉所有监听这个事件的监听器,马上要关闭Spring容器了,这里其实也是一个扩展点,即通过Springboot的事件监听机制,也可以在Spring容器关闭的时候自定义一些操作;
紧接着停止Spring bean生命周期里的所有bean,销毁Spring容器内所有缓存的单例bean,Dog类就在销毁之列,实际上Dog.destroy()方法执行时机就在这;
最后才是真正的开始Spring容器的关闭;
protected void doClose() {
// Check whether an actual close attempt is necessary...
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// Spring容器关闭的时候,会发布一个ContextClosedEvent事件
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 停止Spring bean生命周期里的所有bean
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
//销毁Spring容器内所有缓存的单例bean
destroyBeans();
// 关闭Spring容器
closeBeanFactory();
onClose();
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
this.active.set(false);
}
}
顺着destroyBeans()继续往执行,在DefaultSingletonBeanRegistry#destroySingletons中,找到了触发Dog.destroy()执行的位置
public void destroySingletons() {
if (logger.isTraceEnabled()) {
logger.trace("Destroying singletons in " + this);
}
synchronized (this.singletonObjects) {
this.singletonsCurrentlyInDestruction = true;
}
String[] disposableBeanNames;
//所有DisposableBean的实现类都已经在disposableBeans缓存
synchronized (this.disposableBeans) {
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
//这里真接遍历一遍调用,朴实无华
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
this.containedBeanMap.clear();
this.dependentBeanMap.clear();
this.dependenciesForBeanMap.clear();
clearSingletonCache();
}
总结
仔细琢磨一翻会发现,DisposableBean这个扩展点很简单,似乎没什么用,只有一个扩展方法destroy(),其触发时机也是在Spring容器关闭、销毁bean的时候 ,但很关键。你想呀,我们使用Springboot作为项目的开发框架,业务实际上是跑在Spring容器里的,如果Spring容器关闭的时候,业务还正在执行,这不是要出大乱子吗?所以你说这个接口有用没?肯定有用呀,优雅安全的做法就是,在Spring容器关闭,通过这个扩展接口,提前安排好相关的业务资源释放,防止出现一些不可控的业务错误。