關閉鉤子(shutdown hook)的作用以及在Tomcat中的使用
在很多實際應用環境中,當用戶關了應用程式時,需要做一些善後清理工作,但問題是,使用者有時並不會按照推薦的方法關閉應用程式,很有可能不做清理工作,例如在Tomcat的部署應用中,通過例項化一個Server物件來啟動servlet容器,並呼叫其start方法,然後逐個呼叫元件的start方法,正常情況下,為了讓Server物件能夠關閉這些已經啟動的元件,你應該向指定的埠傳送關閉命令,如果你只是簡單的突然退出,例如在應用程式過程中關閉控制檯,可能會發生一些意想不到的事情。
幸運的是,java為程式設計師提供了一種優雅的方法可以在關閉過程中執行一些程式碼,這樣就能確保那些負責善後處理的程式碼肯定能夠執行,下面將展示如何關閉鉤子來確保清理程式碼總是能夠執行,無論使用者如何終止程式。
在java中,虛擬機器會對兩類事件進行響應,然後執行關閉操作,
- 當呼叫System.exit()方法或者程式的最後一個非守護程序執行緒退出時,應用程式正常退出
- 使用者突然強制虛擬機器中斷執行,例如使用者按CTRL+C快捷鍵或在為關閉Java程式的情況下,從系統中退出。
虛擬機器在執行關閉操作時,會經過以下兩個階段
- 虛擬機器啟動所有已經註冊的關閉鉤子,如果有的話,關閉鉤子是先前已經通過Runtime類註冊的執行緒,所有的關閉鉤子會併發執行,直到完成任務
- 虛擬機器根據情況呼叫所有沒有被呼叫過的終結器(finalizer)
下面重點說明第一個階段,因為該階段允許程式設計師告訴虛擬機器在應用程式中執行一些清理程式碼。關閉鉤子 很簡單,只是 java.lang.Thread類的一個子類例項,建立關閉鉤子也很簡單:
- 建立Thread類的一個子類
- 實現你自己的run方法,當應用程式(正常或者突然)關閉時,會呼叫此方法
- 在應用程式中,例項化 關閉鉤子類
- 使用當前Runtime類的addShutdownHook方法註冊關閉鉤子
也許你已經注意到了,不需要像啟動執行緒一樣呼叫關閉鉤子的start方法,虛擬機器會在它執行其關閉程式時啟動並執行關閉鉤子。
下面定義了一個簡單的ShutdownHookDemo類和一個Thread類(ShutdownHookDemo類)的子類,,注意,ShutdownHook類的run方法只是簡單的將字串“Shutting down”輸出到控制檯上,但是可以插入想在應用程式關閉之前的任何程式碼。
1 package myex16.pyrmont.shutdownhook; 2 3 import java.io.IOException; 4 5 /** 6 * <p> 7 * <b>Title:ShutdownHookDemo.java</b> 8 * </p> 9 * <p> 10 * Copyright:ChenDong 2018 11 * </p> 12 * <p> 13 * Company:僅學習時使用 14 * </p> 15 * <p> 16 * 類功能描述: 演示 關閉鉤子 的簡單實用 17 * </p> 18 * 19 * @author 陳東 20 * @date 2018年12月24日 下午8:01:14 21 * @version 1.0 22 */ 23 public class ShutdownHookDemo { 24 25 public void start() { 26 System.out.println("Demo start"); 27 // 建立關閉鉤子 就是執行緒 28 ShutdownHook shutdownHook = new ShutdownHook(); 29 // 像虛擬機器中註冊關閉鉤子 30 Runtime.getRuntime().addShutdownHook(shutdownHook); 31 } 32 33 /** 34 * 35 * <p> 36 * Title: main 37 * </p> 38 * 39 * @date 2018年12月24日 下午8:01:15 40 * 41 * <p> 42 * 功能描述: 43 * </p> 44 * 45 * @param args 46 * 47 */ 48 public static void main(String[] args) { 49 ShutdownHookDemo demo = new ShutdownHookDemo(); 50 demo.start(); 51 52 try { 53 // 等待輸入 這樣 執行緒就不會走完 然後只要隨便輸入東西 就會 走完流程 測試線上程執行完之後 虛擬機器啟動我們註冊的關閉鉤子 並執行 54 System.in.read(); 55 } catch (IOException e) { 56 // TODO Auto-generated catch block 57 e.printStackTrace(); 58 } 59 } 60 61 } 62 63 class ShutdownHook extends Thread { 64 public void run() { 65 System.out.println("Shutting down"); 66 } 67 }
執行結果
Demo start 輸入了東西 Shutting down
在例項化ShutdownHookDemo類後,main方法會呼叫start方法,start方法會建立一個關閉鉤子,並通過RunTime來註冊它:
// 建立關閉鉤子 就是執行緒 28 ShutdownHook shutdownHook = new ShutdownHook(); 29 // 像虛擬機器中註冊關閉鉤子 30 Runtime.getRuntime().addShutdownHook(shutdownHook);
然後,應用程式會等待使用者輸入
System.in.read();
當用戶按Enter鍵時,應用程式退出,但是虛擬機器會執行關閉鉤子,效果是輸出字串“Shutting down”。
關閉鉤子的例子
現在看另一個例子,這是一個簡單的Swing應用程式,其類的名字MySwingApp,效果如圖
該應用程式會在它啟動時建立一個臨時檔案,並在關閉時刪除該臨時檔案。
1 package myex16.pyrmont.shutdownhook; 2 3 import java.awt.Rectangle; 4 import java.awt.event.ActionEvent; 5 import java.io.File; 6 import java.io.IOException; 7 8 import javax.swing.JButton; 9 import javax.swing.JFrame; 10 import javax.swing.JTextArea; 11 12 /** 13 * <p> 14 * <b>Title:MySwingApp.java</b> 15 * </p> 16 * <p> 17 * Copyright:ChenDong 2018 18 * </p> 19 * <p> 20 * Company:僅學習時使用 21 * </p> 22 * <p> 23 * 類功能描述:演示 關閉鉤子的使用 24 * </p> 25 * 26 * @author 陳東 27 * @date 2018年12月24日 下午8:27:54 28 * @version 1.0 29 */ 30 public class MySwingApp extends JFrame { 31 32 JButton exitButton = new JButton(); 33 34 JTextArea jTextArea1 = new JTextArea(); 35 36 String dir = System.getProperty("user.dir"); 37 String filename = "temp.txt"; 38 39 public MySwingApp() { 40 exitButton.setText("Exit"); 41 exitButton.setBounds(new Rectangle(304, 248, 76, 37)); 42 exitButton.addActionListener(new java.awt.event.ActionListener() { 43 44 @Override 45 public void actionPerformed(ActionEvent e) { 46 exitButton_actionPerformed(e); 47 } 48 49 }); 50 51 this.getContentPane().setLayout(null); 52 jTextArea1.setText("Click the Exit button to quit"); 53 jTextArea1.setBounds(new Rectangle(9, 7, 371, 235)); 54 this.getContentPane().add(exitButton, null); 55 this.getContentPane().add(jTextArea1, null); 56 this.setDefaultCloseOperation(EXIT_ON_CLOSE); 57 this.setBounds(0, 0, 400, 330); 58 this.setVisible(true); 59 initialize(); 60 } 61 62 private void initialize() { 63 // 建立一個temp.txt檔案 64 File file = new File(dir, filename); 65 try { 66 System.out.println("Creating temporary file"); 67 file.createNewFile(); 68 } catch (IOException e) { 69 System.out.println("Failed creating temporary file."); 70 } 71 } 72 73 /** 74 * 75 * <p> 76 * Title: main 77 * </p> 78 * 79 * @date 2018年12月24日 下午8:27:54 80 * 81 * <p> 82 * 功能描述: 83 * </p> 84 * 85 * @param args 86 * 87 */ 88 public static void main(String[] args) { 89 90 MySwingApp mySwingApp = new MySwingApp(); 91 } 92 93 private void shutdown() { 94 // 刪除這個檔案 95 File file = new File(dir, filename); 96 if (file.exists()) { 97 System.out.println("Deleting temporary file."); 98 file.delete(); 99 } 100 } 101 102 void exitButton_actionPerformed(ActionEvent e) { 103 shutdown(); 104 System.exit(0); 105 } 106 107 }
在例項化這個類時,應用程式會呼叫其initialize方法,然後initialize方法會在使用者目錄下建立一個臨時檔案,名為"temp.txt"
private void initialize() { // 建立一個temp.txt檔案 File file = new File(dir, filename); try { System.out.println("Creating temporary file"); file.createNewFile(); } catch (IOException e) { System.out.println("Failed creating temporary file."); } }
當用戶關閉應用程式時,應用程式需要刪除該臨時檔案,我們希望使用者總是能夠通過單擊Exit按鈕來退出,這樣就會呼叫shutdown方法,也就可以刪除臨時檔案了,但是如果使用者是通過點選右上角的關閉按鈕或者是通過其他方法退出的,臨時檔案就無法刪除了,
下面給的類提供了這個問題的解決方案,使用關閉鉤子來刪除臨時檔案,關閉鉤子的類是一個內部類,這樣它就訪問主類的所有方法了,在下面程式碼中,關閉鉤子的run方法會呼叫shutdown方法,保證在虛擬機器關閉時會呼叫shutdown方法。
package myex16.pyrmont.shutdownhook; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTextArea; /** * <p> * <b>Title:MySwingApp.java</b> * </p> * <p> * Copyright:ChenDong 2018 * </p> * <p> * Company:僅學習時使用 * </p> * <p> * 類功能描述:演示 關閉鉤子的使用 * </p> * * @author 陳東 * @date 2018年12月24日 下午8:27:54 * @version 1.0 */ public class MySwingApp extends JFrame { JButton exitButton = new JButton(); JTextArea jTextArea1 = new JTextArea(); String dir = System.getProperty("user.dir"); String filename = "temp.txt"; public MySwingApp() { exitButton.setText("Exit"); exitButton.setBounds(new Rectangle(304, 248, 76, 37)); exitButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(ActionEvent e) { exitButton_actionPerformed(e); } }); this.getContentPane().setLayout(null); jTextArea1.setText("Click the Exit button to quit"); jTextArea1.setBounds(new Rectangle(9, 7, 371, 235)); this.getContentPane().add(exitButton, null); this.getContentPane().add(jTextArea1, null); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setBounds(0, 0, 400, 330); this.setVisible(true); initialize(); } private void initialize() { MyShutdownHook hook = new MyShutdownHook(); Runtime.getRuntime().addShutdownHook(hook); // 建立一個temp.txt檔案 File file = new File(dir, filename); try { System.out.println("Creating temporary file"); file.createNewFile(); } catch (IOException e) { System.out.println("Failed creating temporary file."); } } /** * * <p> * Title: main * </p> * * @date 2018年12月24日 下午8:27:54 * * <p> * 功能描述: * </p> * * @param args * */ @SuppressWarnings("unused") public static void main(String[] args) { MySwingApp mySwingApp = new MySwingApp(); } private void shutdown() { // 刪除這個檔案 File file = new File(dir, filename); if (file.exists()) { System.out.println("Deleting temporary file."); file.delete(); } } void exitButton_actionPerformed(ActionEvent e) { shutdown(); System.exit(0); } @SuppressWarnings("unused") private class MyShutdownHook extends Thread { public void run() { shutdown(); } } }
注意 這次的initialize方法,他首先會建立內部類MyShutdownHook的一個例項,該類繼承自java.lang.Thread類
MyShutdownHook hook = new MyShutdownHook();
一旦獲得了MyShutdownHook類的例項後。就需要將其值傳給Rutime類的addShutDownhook方法
Runtime.getRuntime().addShutdownHook(hook);
initialize方法剩餘程式碼就與上一個示例類似了 建立臨時檔案,
現在啟動上面程式碼,檢查一下,當突然關閉應用程式時,是否總是刪除臨時檔案。
注意:關閉鉤子 的run方法總會執行,
將上面例子中的關閉鉤子的run方法替換一下
@SuppressWarnings("unused") private class MyShutdownHook extends Thread { public void run() { System.out.println("關閉鉤子執行了"); shutdown(); } }
然後在執行示例,在通過點選按鈕正常退出時輸出如下
Creating temporary file Deleting temporary file. 關閉鉤子執行了
通過點選 右上角的 X 關閉輸出如下
Creating temporary file 關閉鉤子執行了 Deleting temporary file.
注意一下 鉤子run執行的 順序
- 第一種情況:正常關閉時 是在 執行完shutdown方法之後 虛擬機器執行的 關閉鉤子
- 第二種: 非正常時,是在發生被點選X 之後, 虛擬機器執行的關閉鉤子
只要有關閉鉤子 那麼除非進行登出 否則一定會被執行
Tomcat中的關閉鉤子
那麼重點來了 既然在Tomcat學習中將這個肯定是 ,Tomcat也用到了關閉鉤子來完成退出過程的,在 org,apache.catalina.startup.Catalina類中,可以找到這樣的程式碼,Catalina類負責啟動管理其他元件的Srver物件。一個名為CatalinaShutdownHook的內部類繼承自Thread類,提供了run方法的實現,它呼叫server物件的stop方法,執行關閉操作,
/** * * 關閉鉤,這將執行清潔關閉 Catalina */ protected class CatalinaShutdownHook extends Thread { public void run() { if (server != null) { try { ((Lifecycle) server).stop(); } catch (LifecycleException e) { System.out.println("Catalina.stop: " + e); e.printStackTrace(System.out); if (e.getThrowable() != null) { System.out.println("----- Root Cause -----"); e.getThrowable().printStackTrace(System.out); } } } } }
在Catalina例項啟動時,會例項化關閉鉤子,並在一個階段將其新增到Rutime類中,
有時候,應用程式在關閉之前應該執行一些程式碼清理工作,但是你不能價設定使用者總是正常退出,那麼這次介紹的關閉鉤子提供了一種解決方案,確保無論使用者如何關閉應用程式,清理程式碼總是能得到執行。