前言
Tomcat的功能是由一個(gè)個(gè)的組件堆砌和架構(gòu)出來(lái)的。幾乎每個(gè)組件都是生命周期組件。何為生命周期組件呢?就是可以人為地對(duì)組件實(shí)施生命周期方法。那么生命周期方法又有哪些呢?我們查看生命周期接口Lifecycle得知以下方法。
-
init()// 初始化 -
start()// 啟動(dòng) -
stop()// 停止 -
destroy()// 銷毀
通過(guò)這些方法,我們可以掌控組件的生與死。init()和start()在啟動(dòng)的時(shí)候調(diào)用,而stop()和destroy()在停止的時(shí)候調(diào)用,他們互為逆操作。我們?cè)诜治鰐omcat的各個(gè)組件的時(shí)候,更多地關(guān)注了啟動(dòng)過(guò)程,卻往往忽略了停止過(guò)程。
那么tomcat是通過(guò)哪種機(jī)制來(lái)停止組件的呢?這么多組件又是按照怎樣的順序來(lái)停止的呢?這就是本章需要關(guān)注的技術(shù)點(diǎn)--關(guān)閉鉤子。
jdk中的關(guān)閉鉤子
在java進(jìn)程運(yùn)行的過(guò)程中,我們開啟了很多資源,而這些資源在java進(jìn)程停止的時(shí)候必須得到清理和釋放,以避免資源的浪費(fèi)。那么,我們對(duì)java進(jìn)程進(jìn)行停止操作(比如kill -15),java進(jìn)程是否有一種機(jī)制來(lái)感知到,然后做一些處理呢?
答案是可以~!
我們可以java進(jìn)程啟動(dòng)完成之前,通過(guò)Runtime.getRuntime().addShutdownHook(shutdownHook);方法注冊(cè)一個(gè)關(guān)閉鉤子。關(guān)閉鉤子是一個(gè)特殊的線程,可以人為編碼實(shí)現(xiàn)。java進(jìn)程在停止之前,會(huì)調(diào)用已注冊(cè)關(guān)閉鉤子的run()方法。
我們寫一個(gè)例子來(lái)看看效果。
public class ShutdownHookDemo {
public static void main(String[] args) throws Exception {
System.out.println("process is running");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("process is down. now, you can close the related resources.");
}));
Thread.sleep(Long.MAX_VALUE);
}
}
當(dāng)我們停止進(jìn)程之后,得到如下輸出。

由此可以驗(yàn)證,java自帶的關(guān)閉鉤子是可以生效的。
【警告】例外的情況就是,當(dāng)我們對(duì)java進(jìn)程執(zhí)行
kill -9命令的時(shí)候,關(guān)閉鉤子并不會(huì)被執(zhí)行,這一點(diǎn)請(qǐng)大家一定注意!
tomcat中關(guān)閉鉤子的運(yùn)用
tomcat中關(guān)閉鉤子是在Catalina.start()方法中聲明的,我們來(lái)看一看。
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
// 注冊(cè)關(guān)閉鉤子
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
關(guān)鍵代碼就三行,為了突出重點(diǎn),我們單獨(dú)將這3行代碼粘貼出來(lái)看看,是不是和我們前面寫的例子很相似?
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
接下來(lái)我們分析一下CatalinaShutdownHook,關(guān)鍵代碼就一行Catalina.this.stop();,停止Catalina框架,代碼也是非常的簡(jiǎn)單和簡(jiǎn)潔。
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}
總結(jié)
- 首先,我們拋出了為什么要有
關(guān)閉鉤子,是因?yàn)槲覀冃枰趈ava進(jìn)程關(guān)閉的時(shí)候進(jìn)行一些資源的清理和釋放操作 - 其次,我們自行編寫了一個(gè)
關(guān)閉鉤子的例子來(lái)看看其用法 - 最后,我們通過(guò)分析tomcat中
關(guān)閉鉤子的源碼,加深了我們隊(duì)關(guān)閉鉤子的理解