java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Springboot扩展点DisposableBean

关于Springboot的扩展点DisposableBean的原理解析

作者:凡夫贩夫

这篇文章主要介绍了关于Springboot的扩展点DisposableBean的原理解析,DisposableBean是一个接口,为Spring bean提供了一种释放资源的方式 ,只有一个扩展方法destroy(),需要的朋友可以参考下

前言

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

到此这篇关于关于Springboot的扩展点DisposableBean的原理解析的文章就介绍到这了,更多相关Springboot扩展点DisposableBean内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文