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

 更新时间:2023年05月19日 11:35:11   作者:凡夫贩夫  
这篇文章主要介绍了关于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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Maven Spring jar包启动报错问题解决方案

    Maven Spring jar包启动报错问题解决方案

    maven 编译jar包,放在linux服务器启动不起来,提示:xxxx-0.0.1-SNAPSHOT.jar中没有主清单属性,接下来通过本文给大家分享问题原因及解决方案,感兴趣的朋友跟随小编一起看看吧
    2023-10-10
  • java使用tess4j进行图片文字识别功能

    java使用tess4j进行图片文字识别功能

    Tess4J 是Java (JNA) 对 Tesseract OCR API 的封装,Tess4J是java直接可使用的jar包,而Tesseract OCR是支持Tess4J进文件文字识别的基础,Tess4J可直接使用Maven方式引入,这篇文章主要介绍了java使用tess4j进行图片文字识别,需要的朋友可以参考下
    2023-04-04
  • Java中的MapStruct实现详解

    Java中的MapStruct实现详解

    这篇文章主要介绍了Java中的MapStruct实现详解,MapStruct 是一个代码生成器,它基于约定优先于配置的方法大大简化了 JavaBean 类型之间映射的实现,生成的映射代码使用普通方法调用,需要的朋友可以参考下
    2023-11-11
  • Jenkins配置jdk、maven、git方式

    Jenkins配置jdk、maven、git方式

    为了在Jenkins中使用Java、Maven和Git,需要在系统管理的全局工具配置中设置这些工具,首先,确保Jenkins主机已经安装了JDK、Git、Maven以及Maven的settings文件,安装完成后,按照提示配置各个工具
    2023-04-04
  • SpringBoot实现多环境配置文件切换教程详解

    SpringBoot实现多环境配置文件切换教程详解

    很多时候,我们项目在开发环境和生成环境的环境配置是不一样的,例如,数据库配置,这个时候就需要切换环境配置文件。本文将详细讲解SpringBoot如何切换配置文件,需要的可以参考一下
    2022-03-03
  • Spring中BeanFactory FactoryBean和ObjectFactory的三种的区别

    Spring中BeanFactory FactoryBean和ObjectFactory的三种的区别

    关于FactoryBean 和 BeanFactory的对比文章比较多,但是对ObjectFactory的描述就比较少,今天我们对比下这三种的区别,感兴趣的朋友跟随小编一起看看吧
    2023-01-01
  • 你所不知道的Spring的@Autowired实现细节分析

    你所不知道的Spring的@Autowired实现细节分析

    这篇文章主要介绍了你所不知道的Spring的@Autowired实现细节分析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 基于@RequestParam与@RequestBody使用对比

    基于@RequestParam与@RequestBody使用对比

    这篇文章主要介绍了@RequestParam与@RequestBody的使用对比,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 盘点几种常见的java排序算法

    盘点几种常见的java排序算法

    所谓排序就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作,下面这篇文章主要给大家介绍了几种常见的java排序算法的相关资料,需要的朋友可以参考下
    2021-11-11
  • java &与&&的区别及实例

    java &与&&的区别及实例

    这篇文章主要介绍了java &与&&的区别的相关资料,并附简单实例,帮助大家学习理解这部分知识,需要的朋友可以参考下
    2016-10-10

最新评论