關(guān)于Springboot的擴展點DisposableBean的原理解析
前言
DisposableBean,是在Spring容器關(guān)閉的時候預(yù)留的一個擴展點,從業(yè)務(wù)開發(fā)的角度來看,基本上是用不到的,但是Spring容器從啟動到關(guān)閉,是Spring Bean生命周期里一個繞不開的節(jié)點,因此還是有必要學習一下,以便對Spring能有一個更加全面的認識。
功能特性
1、DisposableBean是一個接口,為Spring bean提供了一種釋放資源的方式 ,只有一個擴展方法destroy();
2、實現(xiàn)DisposableBean接口,并重寫destroy(),可以在Spring容器銷毀bean的時候獲得一次回調(diào);
3、destroy()的回調(diào)執(zhí)行時機是Spring容器關(guān)閉,需要銷毀所有的bean時;
實現(xiàn)方式
與InitializingBean比較類似的是,InitializingBean#afterPropertiesSet()是在bean初始化的時候觸發(fā)執(zhí)行,DisposableBean#destroy()是在bean被銷毀的時候觸發(fā)執(zhí)行,這里結(jié)合Springboot擴展點之InitializingBean,用一個示例分析一下DisposableBean擴展接口的相關(guān)特性:
1、定義Dog類,實現(xiàn)InitializingBean、DisposableBean接口,并重寫afterPropertiesSet()、destroy()
@Slf4j public class Dog implements InitializingBean, DisposableBean { private String name = "wang cai"; private Food food; public Dog() { log.info("----Dog的無參構(gòu)造方法被執(zhí)行"); } @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觸發(fā)執(zhí)行"); } public void myInitMethod() { log.info("----com.fanfu.entity.Dog.myInitMethod觸發(fā)執(zhí)行"); } @Override public void destroy() throws Exception { log.info("----com.fanfu.entity.Dog.destroy觸發(fā)執(zhí)行"); } }
2、單元測試也比較簡單,先啟動Spring容器,然后再優(yōu)雅地關(guān)閉;
@Test public void test5(){ log.info("----單元測試執(zhí)行開始"); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu"); log.info("----開始關(guān)閉Spring容器"); context.registerShutdownHook(); log.info("----Spring容器已經(jīng)關(guān)閉完成"); log.info("----單元測試執(zhí)行完畢"); }
單元測試執(zhí)行結(jié)果:
從單元測試的執(zhí)行結(jié)果來看,Spring容器關(guān)閉后,會觸發(fā)執(zhí)行DisposableBean#destroy()擴展方法的執(zhí)行,所以如果我們的業(yè)務(wù)開發(fā)中,如果某些Bean在容器關(guān)閉后,需要做一些釋放業(yè)務(wù)資源之類的操作,就能用到這個擴展點了。有的小伙伴也許會有疑問:上面為什么單元測試執(zhí)行完了,才觸發(fā)Dog.destroy()方法執(zhí)行的?其實是這樣的,你仔細觀察會發(fā)現(xiàn),觸發(fā)Dog.destroy()方法執(zhí)行并不是主線程,而是叫做SpringContextShutdownHook的線程,這里用到了多線程技術(shù),單元測試執(zhí)行完了,才觸發(fā)Dog.destroy()方法執(zhí)行是多線程異步執(zhí)行的原因。
@Override public void registerShutdownHook() { if (this.shutdownHook == null) { // 多線程執(zhí)行容器關(guān)閉的操作,主要邏輯在doClose() this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) { @Override public void run() { synchronized (startupShutdownMonitor) { doClose(); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }
工作原理
從實現(xiàn)方式示例中,可以了解Spring容器關(guān)閉時,使用了多線程技術(shù)調(diào)用了doClose()來完成相關(guān)操作,然后觸發(fā)了DisposableBean#destroy()擴展方法的執(zhí)行。
doClose()中的邏輯也相對簡單,先發(fā)布一個ContextClosedEvent事件,告訴所有監(jiān)聽這個事件的監(jiān)聽器,馬上要關(guān)閉Spring容器了,這里其實也是一個擴展點,即通過Springboot的事件監(jiān)聽機制,也可以在Spring容器關(guān)閉的時候自定義一些操作;
緊接著停止Spring bean生命周期里的所有bean,銷毀Spring容器內(nèi)所有緩存的單例bean,Dog類就在銷毀之列,實際上Dog.destroy()方法執(zhí)行時機就在這;
最后才是真正的開始Spring容器的關(guān)閉;
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容器關(guān)閉的時候,會發(fā)布一個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容器內(nèi)所有緩存的單例bean destroyBeans(); // 關(guān)閉Spring容器 closeBeanFactory(); onClose(); if (this.earlyApplicationListeners != null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } this.active.set(false); } }
順著destroyBeans()繼續(xù)往執(zhí)行,在DefaultSingletonBeanRegistry#destroySingletons中,找到了觸發(fā)Dog.destroy()執(zhí)行的位置
public void destroySingletons() { if (logger.isTraceEnabled()) { logger.trace("Destroying singletons in " + this); } synchronized (this.singletonObjects) { this.singletonsCurrentlyInDestruction = true; } String[] disposableBeanNames; //所有DisposableBean的實現(xiàn)類都已經(jīng)在disposableBeans緩存 synchronized (this.disposableBeans) { disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet()); } //這里真接遍歷一遍調(diào)用,樸實無華 for (int i = disposableBeanNames.length - 1; i >= 0; i--) { destroySingleton(disposableBeanNames[i]); } this.containedBeanMap.clear(); this.dependentBeanMap.clear(); this.dependenciesForBeanMap.clear(); clearSingletonCache(); }
總結(jié)
仔細琢磨一翻會發(fā)現(xiàn),DisposableBean這個擴展點很簡單,似乎沒什么用,只有一個擴展方法destroy(),其觸發(fā)時機也是在Spring容器關(guān)閉、銷毀bean的時候 ,但很關(guān)鍵。你想呀,我們使用Springboot作為項目的開發(fā)框架,業(yè)務(wù)實際上是跑在Spring容器里的,如果Spring容器關(guān)閉的時候,業(yè)務(wù)還正在執(zhí)行,這不是要出大亂子嗎?所以你說這個接口有用沒?肯定有用呀,優(yōu)雅安全的做法就是,在Spring容器關(guān)閉,通過這個擴展接口,提前安排好相關(guān)的業(yè)務(wù)資源釋放,防止出現(xiàn)一些不可控的業(yè)務(wù)錯誤。
到此這篇關(guān)于關(guān)于Springboot的擴展點DisposableBean的原理解析的文章就介紹到這了,更多相關(guān)Springboot擴展點DisposableBean內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ?IDEA?2022.1.1創(chuàng)建java項目的詳細方法步驟
最近安裝了IntelliJ IDEA 2022.1.1,發(fā)現(xiàn)新版本的窗口還有些變化的,所以下面這篇文章主要給大家介紹了關(guān)于IntelliJ?IDEA?2022.1.1創(chuàng)建java項目的詳細方法步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2022-07-07如何使用try-with-resource機制關(guān)閉連接
這篇文章主要介紹了使用try-with-resource機制關(guān)閉連接的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Springboot Thymeleaf實現(xiàn)HTML屬性設(shè)置
這篇文章主要介紹了Springboot Thymeleaf實現(xiàn)HTML屬性設(shè)置,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2007-11-11SpringBoot如何使用@Aspect注解實現(xiàn)AOP
這篇文章主要介紹了SpringBoot如何使用@Aspect注解實現(xiàn)AOP問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07Spring核心IoC容器的依賴注入接口和層級包命名規(guī)范
這篇文章主要介紹了Spring核心IoC容器的依賴注入接口和層級包命名規(guī)范,IOC又名控制反轉(zhuǎn),把對象創(chuàng)建和對象之間的調(diào)用過程,交給Spring進行管理,目的是為了降低耦合度,需要的朋友可以參考下2023-05-05Java注解之Retention、Documented、Inherited介紹
這篇文章主要介紹了Java注解之Retention、Documented、Inherited注解介紹,本文內(nèi)容和相關(guān)文章是系列文章,需要的朋友可以參考下2014-09-09