关于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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SWT(JFace) 打印功能

    SWT(JFace) 打印功能

    SWT(JFace)体验之打印功能
    2009-06-06
  • java基于JSON实现前后端交互(附代码)

    java基于JSON实现前后端交互(附代码)

    本文主要介绍了java基于JSON实现前后端交互,通过实际代码示例展示了如何前后端JSON交互,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • idea每次新打开的项目窗口maven都要重新设置问题

    idea每次新打开的项目窗口maven都要重新设置问题

    这篇文章主要介绍了idea每次新打开的项目窗口maven都要重新设置问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Spring Boot中@Autowired注入为空的原因以及解决方法

    Spring Boot中@Autowired注入为空的原因以及解决方法

    最近在开发中遇到了使用@Autowired注解自动装配时会报空指针,发现对象并没有装配进来,下面这篇文章主要给大家介绍了关于Spring Boot中@Autowired注入为空的原因以及解决方法,需要的朋友可以参考下
    2024-01-01
  • Java多线程中的wait/notify通信模式实例详解

    Java多线程中的wait/notify通信模式实例详解

    这篇文章主要给大家介绍了关于Java多线程中wait/notify通信模式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 利用Java实现轻松解析DNS报文

    利用Java实现轻松解析DNS报文

    这篇文章主要为大家详细介绍了如何利用Java实现轻松解析DNS报文,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-11-11
  • SpringCloud通过Nacos实现注册中心与远程服务调用详解流程

    SpringCloud通过Nacos实现注册中心与远程服务调用详解流程

    如果不满足eureka注册中心,那么本文记录的Nacos是不二之选。本文主要记录Springboot基于Nacos实现注册中心以及远程服务调用
    2022-07-07
  • SpringBoot Actuator未授权访问漏洞修复详解

    SpringBoot Actuator未授权访问漏洞修复详解

    这篇文章主要介绍了SpringBoot Actuator未授权访问漏洞修复详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • SpringBoot请求参数相关注解说明小结

    SpringBoot请求参数相关注解说明小结

    这篇文章主要介绍了SpringBoot请求参数相关注解说明,主要包括@PathVariable,@RequestHeader、@CookieValue、@RequestBody和@RequestParam,本文结合实例代码给大家讲解的非常详细,需要的朋友可以参考下
    2022-05-05
  • springSecurity实现简单的登录功能

    springSecurity实现简单的登录功能

    这篇文章主要为大家详细介绍了springSecurity实现简单的登录功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09

最新评论