前言:
又是一個之前沒關注過的課題,發現學習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