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容器关闭,通过这个扩展接口,提前安排好相关的业务资源释放,防止出现一些不可控的业务错误。