Spring優雅關閉之:ShutDownHook

前言:

    又是一個之前沒關注過的課題,發現學習Spring官方文檔還是有用的,一個個的知識點不斷冒出來。

    意外的發現朱小廝https://blog.csdn.net/u013256816/ 大神也是CSDN重度患者,哈哈,向大神學習,好好寫博客,應該有一天也可以出書的吧。

 

    閒話不多說了,先提出一個問題,什麼叫做優雅關閉?

    我們的java程序運行在JVM上,有很多情況可能會突然崩潰掉,比如OOM、用戶強制退出、業務其他報錯。。。等一系列的問題可能導致我們的進程掛掉。如果我們的進程在運行一些很重要的內容,比如事務操作之類的,很有可能導致事務的不一致性問題。所以,實現應用的優雅關閉還是蠻重要的,起碼我們可以在關閉之前做一些記錄補救操作。

    

1.如何補救?

    在java程序中,可以通過添加關閉鉤子函數,實現在程序退出時關閉資源、平滑退出的功能。

    如何做呢?

    主要就是通過Runtime.addShutDownHook(Thread hook)來實現的。下面我們來簡單看一個示例

 

2.Runtime.addShutDownHook(Thread hook)

// 創建HookTest,我們通過main方法來模擬應用程序
public class HookTest {

    public static void main(String[] args) {

        // 添加hook thread,重寫其run方法
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                System.out.println("this is hook demo...");
                // TODO
            }
        });

        int i = 0;
        // 這裏會報錯,我們驗證寫是否會執行hook thread
        int j = 10/i;
        System.out.println("j" + j);
    }
}

// res
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at hook.HookTest.main(HookTest.java:23)
this is hook demo...

Process finished with exit code 1

    總結:我們主動寫了一個報錯程序,在程序報錯之後,鉤子函數還是被執行了。經驗證,我們是可以通過對Runtime添加鉤子函數來做到優雅停機。

 

3.Runtime.addShutDownHook(Thread hook)應用場景

    既然JDK提供的這個方法可以註冊一個JVM關閉的鉤子函數,那麼這個函數在什麼情況下會被調用呢?上述我們展示了在程序異常情況下會被調用,還有沒有其他場景呢?

    * 程序正常退出

    * 使用System.exit()

    * 終端使用Ctrl+C觸發的中斷

    * 系統關閉

    * OutofMemory宕機

    * 使用Kill pid殺死進程(使用kill -9是不會被調用的)

 

4.Spring如何添加鉤子函數

    1)Spring添加鉤子函數比較簡單,如下

// 通過這種方式來添加鉤子函數
ApplicationContext.registerShutdownHook();

// 通過源碼可以看到,
@Override
public void registerShutdownHook() {
    if (this.shutdownHook == null) {
        // No shutdown hook registered yet.
        this.shutdownHook = new Thread() {
            @Override
            public void run() {
                synchronized (startupShutdownMonitor) {
                    doClose();
                }
            }
        };
        // 也是通過這種方式來添加
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

// 重點是這個doClose()方法

protected void doClose() {
    // Check whether an actual close attempt is necessary...
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
        if (logger.isInfoEnabled()) {
            logger.info("Closing " + this);
        }

        LiveBeansView.unregisterApplicationContext(this);

        try {
            // Publish shutdown event.
            publishEvent(new ContextClosedEvent(this));
        }
        catch (Throwable ex) {
            logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
        }

        // Stop all Lifecycle beans, to avoid delays during individual destruction.
        if (this.lifecycleProcessor != null) {
            try {
                this.lifecycleProcessor.onClose();
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
            }
        }

        // Destroy all cached singletons in the context's BeanFactory.
        destroyBeans();

        // Close the state of this context itself.
        closeBeanFactory();

        // Let subclasses do some final clean-up if they wish...
        onClose();

        // Switch to inactive.
        this.active.set(false);
    }
}

    可以看到:doClose()方法會執行bean的destroy(),也會執行SmartLifeCycle的stop()方法,我們就可以通過重寫這些方法來實現對象的關閉,生命週期的管理,實現平滑shutdown

 

    2)測試鉤子

// 1.我們之前生命週期管理的SmartLifeCycleDemo    
// 參考Spring容器生命週期管理:SmartLifecycle

// 2.單元測試類,創建一個ApplicationContext
@Test
public void testXml(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    // 註冊鉤子
    applicationContext.registerShutdownHook();
}    

// 我們可以對比下注冊鉤子與不註冊的區別:
// 1)不註冊,只創建容器,結果如下:
start // 只輸出start,說明只執行了SmartLifeCycleDemo.start()方法
    
// 2)註冊鉤子
start
stop(Runnable) // 當main方法執行結束時,主動執行了SmartLifeCycleDemo.stop()方法

    總結:通過註冊鉤子函數,可以在程序停止前執行我們自定義的各種destroy()或者stop()方法,用於優雅關閉。

    注意:我們可以對比上一篇關於SmartLifeCycle的文章中(https://blog.csdn.net/qq_26323323/article/details/89814304  )關於SmartLifeCycleDemo的測試,那個也是輸出了stop,但是是因爲主動調用了applicationContext.stop()方法所以才輸出的,我們當前並沒有主動調用stop()方法

 

5.總結

    我們通過調用ApplicationContext.registerShutdownHook()來註冊鉤子函數,實現bean的destroy,Spring容器的關閉,通過實現這些方法來實現平滑關閉。

    注意:我們當前討論的都是Spring非Web程序,如果是Web程序的話,不需要我們來註冊鉤子函數,Spring的Web程序已經有了相關的代碼實現優雅關閉了。

 

參考:

https://docs.spring.io/spring/docs/4.3.23.RELEASE/spring-framework-reference/htmlsingle/ 

代碼地址:https://github.com/kldwz/springstudy  

 


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章