關閉鉤子(shutdown hook)的做用以及在Tomcat中的使用

 

  在不少實際應用環境中,當用戶關了應用程序時,須要作一些善後清理工做,但問題是,用戶有時並不會按照推薦的方法關閉應用程序,頗有可能不作清理工做,例如在Tomcat的部署應用中,經過實例化一個Server對象來啓動servlet容器,並調用其start方法,而後逐個調用組件的start方法,正常狀況下,爲了讓Server對象可以關閉這些已經啓動的組件,你應該向指定的端口發送關閉命令,若是你只是簡單的忽然退出,例如在應用程序過程當中關閉控制檯,可能會發生一些意想不到的事情。java

  幸運的是,java爲程序員提供了一種優雅的方法能夠在關閉過程當中執行一些代碼,這樣就能確保那些負責善後處理的代碼確定可以執行,下面將展現如何關閉鉤子來確保清理代碼老是可以執行,不管用戶如何終止程序。程序員

  在java中,虛擬機會對兩類事件進行響應,而後執行關閉操做,apache

  • 當調用System.exit()方法或者程序的最後一個非守護進程線程退出時,應用程序正常退出
  • 用戶忽然強制虛擬機中斷運行,例如用戶按CTRL+C快捷鍵或在爲關閉Java程序的狀況下,從系統中退出。

    虛擬機在執行關閉操做時,會通過如下兩個階段併發

  1. 虛擬機啓動全部已經註冊的關閉鉤子,若是有的話,關閉鉤子是先前已經經過Runtime類註冊的線程,全部的關閉鉤子會併發執行,直到完成任務
  2. 虛擬機根據狀況調用全部沒有被調用過的終結器(finalizer)

  下面重點說明第一個階段,由於該階段容許程序員告訴虛擬機在應用程序中執行一些清理代碼。關閉鉤子 很簡單,只是 java.lang.Thread類的一個子類實例,建立關閉鉤子也很簡單:ide

  1. 建立Thread類的一個子類
  2. 實現你本身的run方法,當應用程序(正常或者忽然)關閉時,會調用此方法
  3. 在應用程序中,實例化 關閉鉤子類
  4. 使用當前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 }

運行結果ui

Demo start
輸入了東西
Shutting down

  在實例化ShutdownHookDemo類後,main方法會調用start方法,start方法會建立一個關閉鉤子,並經過RunTime來註冊它:this

// 建立關閉鉤子 就是線程
28         ShutdownHook shutdownHook = new ShutdownHook();
29         // 像虛擬機中註冊關閉鉤子
30         Runtime.getRuntime().addShutdownHook(shutdownHook);

而後,應用程序會等待用戶輸入spa

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 以後,     虛擬機執行的關閉鉤子 

 只要有關閉鉤子 那麼除非進行註銷 不然必定會被執行

 

Runtime.getRuntime().removeShutdownHook(shutdownHook);

 

從Rutime中移除 關閉鉤子,

 

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類中,

 

有時候,應用程序在關閉以前應該執行一些代碼清理工做,可是你不能價設定用戶老是正常退出,那麼此次介紹的關閉鉤子提供了一種解決方案,確保不管用戶如何關閉應用程序,清理代碼老是能獲得執行。

相關文章
相關標籤/搜索