關(guān)于Springboot的擴(kuò)展點(diǎn)DisposableBean的原理解析
前言
DisposableBean,是在Spring容器關(guān)閉的時(shí)候預(yù)留的一個(gè)擴(kuò)展點(diǎn),從業(yè)務(wù)開發(fā)的角度來看,基本上是用不到的,但是Spring容器從啟動(dòng)到關(guān)閉,是Spring Bean生命周期里一個(gè)繞不開的節(jié)點(diǎn),因此還是有必要學(xué)習(xí)一下,以便對(duì)Spring能有一個(gè)更加全面的認(rèn)識(shí)。
功能特性
1、DisposableBean是一個(gè)接口,為Spring bean提供了一種釋放資源的方式 ,只有一個(gè)擴(kuò)展方法destroy();
2、實(shí)現(xiàn)DisposableBean接口,并重寫destroy(),可以在Spring容器銷毀bean的時(shí)候獲得一次回調(diào);
3、destroy()的回調(diào)執(zhí)行時(shí)機(jī)是Spring容器關(guān)閉,需要銷毀所有的bean時(shí);
實(shí)現(xiàn)方式
與InitializingBean比較類似的是,InitializingBean#afterPropertiesSet()是在bean初始化的時(shí)候觸發(fā)執(zhí)行,DisposableBean#destroy()是在bean被銷毀的時(shí)候觸發(fā)執(zhí)行,這里結(jié)合Springboot擴(kuò)展點(diǎn)之InitializingBean,用一個(gè)示例分析一下DisposableBean擴(kuò)展接口的相關(guān)特性:
1、定義Dog類,實(shí)現(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、單元測(cè)試也比較簡(jiǎn)單,先啟動(dòng)Spring容器,然后再優(yōu)雅地關(guān)閉;
@Test
public void test5(){
log.info("----單元測(cè)試執(zhí)行開始");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
log.info("----開始關(guān)閉Spring容器");
context.registerShutdownHook();
log.info("----Spring容器已經(jīng)關(guān)閉完成");
log.info("----單元測(cè)試執(zhí)行完畢");
}單元測(cè)試執(zhí)行結(jié)果:

從單元測(cè)試的執(zhí)行結(jié)果來看,Spring容器關(guān)閉后,會(huì)觸發(fā)執(zhí)行DisposableBean#destroy()擴(kuò)展方法的執(zhí)行,所以如果我們的業(yè)務(wù)開發(fā)中,如果某些Bean在容器關(guān)閉后,需要做一些釋放業(yè)務(wù)資源之類的操作,就能用到這個(gè)擴(kuò)展點(diǎn)了。有的小伙伴也許會(huì)有疑問:上面為什么單元測(cè)試執(zhí)行完了,才觸發(fā)Dog.destroy()方法執(zhí)行的?其實(shí)是這樣的,你仔細(xì)觀察會(huì)發(fā)現(xiàn),觸發(fā)Dog.destroy()方法執(zhí)行并不是主線程,而是叫做SpringContextShutdownHook的線程,這里用到了多線程技術(shù),單元測(cè)試執(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);
}
}工作原理
從實(shí)現(xiàn)方式示例中,可以了解Spring容器關(guān)閉時(shí),使用了多線程技術(shù)調(diào)用了doClose()來完成相關(guān)操作,然后觸發(fā)了DisposableBean#destroy()擴(kuò)展方法的執(zhí)行。
doClose()中的邏輯也相對(duì)簡(jiǎn)單,先發(fā)布一個(gè)ContextClosedEvent事件,告訴所有監(jiān)聽這個(gè)事件的監(jiān)聽器,馬上要關(guān)閉Spring容器了,這里其實(shí)也是一個(gè)擴(kuò)展點(diǎn),即通過Springboot的事件監(jiān)聽機(jī)制,也可以在Spring容器關(guān)閉的時(shí)候自定義一些操作;
緊接著停止Spring bean生命周期里的所有bean,銷毀Spring容器內(nèi)所有緩存的單例bean,Dog類就在銷毀之列,實(shí)際上Dog.destroy()方法執(zhí)行時(shí)機(jī)就在這;
最后才是真正的開始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)閉的時(shí)候,會(huì)發(fā)布一個(gè)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的實(shí)現(xiàn)類都已經(jīng)在disposableBeans緩存
synchronized (this.disposableBeans) {
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
//這里真接遍歷一遍調(diào)用,樸實(shí)無華
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
this.containedBeanMap.clear();
this.dependentBeanMap.clear();
this.dependenciesForBeanMap.clear();
clearSingletonCache();
}
總結(jié)
仔細(xì)琢磨一翻會(huì)發(fā)現(xiàn),DisposableBean這個(gè)擴(kuò)展點(diǎn)很簡(jiǎn)單,似乎沒什么用,只有一個(gè)擴(kuò)展方法destroy(),其觸發(fā)時(shí)機(jī)也是在Spring容器關(guān)閉、銷毀bean的時(shí)候 ,但很關(guān)鍵。你想呀,我們使用Springboot作為項(xiàng)目的開發(fā)框架,業(yè)務(wù)實(shí)際上是跑在Spring容器里的,如果Spring容器關(guān)閉的時(shí)候,業(yè)務(wù)還正在執(zhí)行,這不是要出大亂子嗎?所以你說這個(gè)接口有用沒?肯定有用呀,優(yōu)雅安全的做法就是,在Spring容器關(guān)閉,通過這個(gè)擴(kuò)展接口,提前安排好相關(guān)的業(yè)務(wù)資源釋放,防止出現(xiàn)一些不可控的業(yè)務(wù)錯(cuò)誤。
到此這篇關(guān)于關(guān)于Springboot的擴(kuò)展點(diǎn)DisposableBean的原理解析的文章就介紹到這了,更多相關(guān)Springboot擴(kuò)展點(diǎn)DisposableBean內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis-Plus中如何配置加密功能(使用AES算法)
本文將詳細(xì)介紹如何實(shí)現(xiàn) MyBatis-Plus 中的配置加密功能,并給出相應(yīng)的代碼示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Java編程實(shí)現(xiàn)判斷網(wǎng)上鄰居文件是否存在的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)判斷網(wǎng)上鄰居文件是否存在的方法,涉及Java針對(duì)路徑轉(zhuǎn)換及字符串操作的相關(guān)技巧,需要的朋友可以參考下2015-10-10
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(27)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07
詳解Java去除json數(shù)據(jù)中的null空值問題
這篇文章主要介紹了詳解Java去除json數(shù)據(jù)中的null空值問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
SpringBoot?中使用?Validation?校驗(yàn)參數(shù)的方法詳解
Validation?是用于檢查程序代碼中參數(shù)的有效性的框架,作為?Spring?框架中的一個(gè)參數(shù)校驗(yàn)工具,集成在?spring-context?包中,這篇文章主要介紹了SpringBoot?中使用?Validation?校驗(yàn)參數(shù),需要的朋友可以參考下2022-05-05
MyBatis連接數(shù)據(jù)庫配置的基本步驟和機(jī)制
MyBatis 是一個(gè)流行的持久層框架,它通過使用XML或注解的方式將SQL語句、存儲(chǔ)過程和Java方法進(jìn)行綁定,從而避免了手寫大量的JDBC代碼和手動(dòng)設(shè)置參數(shù)與結(jié)果集,本文給大家介紹了MyBatis連接數(shù)據(jù)庫配置的基本步驟和機(jī)制,需要的朋友可以參考下2024-05-05
如何在IDEA啟動(dòng)多個(gè)Spring Boot工程實(shí)例(圖文)
這篇文章主要介紹了如何在IDEA啟動(dòng)多個(gè)Spring Boot工程實(shí)例(圖文),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Java使用Jdbc連接Oracle執(zhí)行簡(jiǎn)單查詢操作示例
這篇文章主要介紹了Java使用Jdbc連接Oracle執(zhí)行簡(jiǎn)單查詢操作,結(jié)合實(shí)例形式詳細(xì)分析了java基于jdbc實(shí)現(xiàn)Oracle數(shù)據(jù)庫的連接與查詢相關(guān)操作技巧,需要的朋友可以參考下2019-09-09

