Chapter 16: Shutdown Hook

Overview

In many circumstances, you need a chance to do clean-up when the user shuts down your application. The problem is, the user does not always follow the recommended procedure to exit. For example, in a Tomcat deployment you start the servlet container by instantiating a Server object and call its start method, which in turn calls the start method of other components. In a normal situation, to give a chance for the Server object to stop all other components, you should close it by sending the proper shutdown command, as explained in Chapter 14, "Server and Service". Something unexpected could happen if you simply exit abruptly, such as by closing the console on which the application is running.

在许多情况下,当用户关闭应用程序时,您需要有机会进行清理工作。

问题是,用户并不总是按照推荐的退出程序的步骤进行操作。

例如,在Tomcat部署中,您通过实例化一个Server对象并调用其start方法来启动servlet容器,该方法又会调用其他组件的start方法。

在正常情况下,为了给Server对象停止所有其他组件的机会,您应该通过发送适当的关闭命令来关闭它,如第14章“服务器和服务”中所述。

如果您简单地突然退出,比如关闭应用程序运行的控制台,可能会发生意外情况。

Fortunately, Java provides an elegant way for programmers to execute code in the middle of a shutdown process, thus making sure your clean-up code is always executed. This chapter shows how to use a shutdown hook to guarantee the clean-up code is always run regardless how the user terminates the application.

幸运的是,Java提供了一种优雅的方式,供程序员在关闭过程中执行代码,以确保您的清理代码始终得到执行。

本章将展示如何使用关闭钩子来确保清理代码始终在用户终止应用程序的方式下运行。

In Java, the virtual machine shuts down itself in response to two types of events:

在Java中,虚拟机响应两种类型的事件来关闭自身:

  • the application exits normally as in the case where the System.exit method is called or when the last non-daemon thread exits.
  • The user abruptly forces the virtual machine to terminate, for example by typing CTRL+C or logs off from the system before closing a running Java program.
  • 应用程序正常退出,例如调用System.exit方法或最后一个非守护线程退出的情况。
  • 用户强制终止虚拟机,例如通过键入CTRL+C或在关闭正在运行的Java程序之前注销系统。

Fortunately enough, when shutting down, the virtual machine follows this two-phase sequence:

幸运的是,在关闭时,虚拟机遵循以下两阶段的顺序:

  1. The virtual machine starts all registered shutdown hooks, if any. Shutdown hooks are threads that are previously registered with the Runtime. All shutdown hooks are run concurrently until they finish.
  2. The virtual machine calls all uninvoked finalizers if appropriate.
  3. 虚拟机会对于所有启动所有已注册的钩子进行关闭(如果有的话)。关闭钩子是先前使用Runtime注册的线程。所有关闭钩子将并发运行,直到它们完成。
  4. 虚拟机会酌情调用所有未调用的终结器。

In this chapter, we are interested in the first phase because this allows programmers to tell the virtual machine to execute some clean up code in the program. A shutdown hook is simply an instance of a subclass of the java.lang.Thread class. Creating a shutdown hook is simple:

在本章中,我们关注第一阶段,因为这允许程序员告诉虚拟机在程序中执行一些清理代码。关闭钩子只是java.lang.Thread类的一个子类的实例。创建一个关闭钩子很简单:

  • Write a class extending the Thread class.
  • Provide the implementation of your class's run method. This method is the code that needs to be run when the application is shut down, either normally or abruptly.
  • In your application, instantiate your shutdown hook class.
  • Register the shutdown hook with the current Runtime's addShutdownHook method.
  • 编写一个扩展Thread类的类。
  • 提供您的类并且实现run方法。这个方法是在应用程序正常或突然关闭时需要运行的代码。
  • 在您的应用程序中,实例化您的关闭钩子类。
  • 使用当前Runtime的addShutdownHook方法注册关闭钩子

As you may have noticed, you do not start the shutdown hook as you would other threads. The virtual machine will start and run your shutdown hook when it runs its shutdown sequence.

正如您可能已经注意到的,您不需要像启动其他线程那样启动关闭钩子。

当虚拟机运行其关闭序列时,它将启动并运行您的关闭钩子。

The code in Listing 16.1 provides a simple class called ShutdownHookDemo and a subclass of Thread named ShutdownHook. Note that the run method of the ShutdownHook class simply prints the string Shutting down on the console. However, you can insert any code that needs to be run before the shutdown.

代码清单16.1提供了一个简单的类ShutdownHookDemo和一个名为ShutdownHook的Thread子类。

请注意,ShutdownHook类的run方法只是在控制台上打印字符串“正在关闭”。

但是,您可以插入任何需要在关闭之前运行的代码。

Listing 16.1: Using Shutdown Hook

代码清单16.1:使用关闭钩子


package ex16.pyrmont.shutdownhook;
public class ShutdownHookDemo {
    public void start() {
        System.out.println("Demo");
        ShutdownHook ShutdownHook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(ShutdownHook);
    }
    public static void main(String[] args) {
        ShutdownHookDemo demo = new ShutdownHookDemo();
        demo.start();
        try {
            System.in.read();
        }
        catch(Exception e) {
        }
    }
}
class ShutdownHook extends Thread {
    public void run() {
        System.out.println("Shutting down");
    }
}

After the instantiating the ShutdownHookDemo class, the main method calls the start method. The start method creates a shutdown hook and registers it with the current runtime.

在实例化 ShutdownHookDemo 类之后,main 方法会调用 start 方法。

start 方法会创建一个关机钩子,并将其注册到当前运行时。

ShutdownHook shutdownHook = new ShutdownHook();
 Runtime.getRuntime().addShutdownHook(shutdownHook);

Then, the program waits for the user to press Enter.

然后,程序等待用户按 Enter 键。

System.in.read();

When the user presses Enter, the program exits. However, the virtual machine will run the shutdown hook, in effect printing the words Shutting down.

当用户按下 Enter 键时,程序退出。

不过,虚拟机将运行关机钩子,实际上打印出关机字样。

A Shutdown Hook Example

As another example, consider a simple Swing application whose class is called MySwingApp (See Figure 16.1). This application creates a temporary file when it is launched. When closed, the temporary file must be deleted.

再举一个简单的 Swing 应用程序为例,它的类名为 MySwingApp(见图 16.1)。该应用程序启动时会创建一个临时文件。关闭时,必须删除临时文件。

image.png

Figure 16.1: A Swing application

图 16.1: Swing 应用程序

The code for this class is given in Listing 16.2.

该类的代码见清单 16.2。

Listing 16.2: A simple Swing application

清单 16.2:一个简单的 Swing 应用程序

  
package ex16.pyrmont.shutdownhook;  
        import java.awt.*;  
        import javax.swing.*;  
        import java.awt.event.*;  
        import java.io.File;  
        import java.io.IOException;  
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() {  
            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() {  
        // create a temp file  
        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.");  
        }  
    }  
    private void shutdown() {  
        // delete the temp file  
        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);  
    }  
    public static void main(String[] args) {  
        MySwingApp mySwingApp = new MySwingApp();  
    }  
}

When run, the application calls its initialize method. The initialize method in turn creates a temporary file called temp.txt in the user's directory:

运行时,应用程序会调用初始化方法。

初始化方法会在用户目录中创建一个名为 temp.txt 的临时文件:

  
private void initialize() {  
    // create a temp file  
    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.");  
    }  
}

When the user closes the application, the application must delete the temporary file. We hope that the user will always click the Exit button because by doing so the shutdown method (which deletes the temporary file) will always gets called. However, the temporary file will not be deleted if the user closes the application by clicking the X button of the frame or by some other means.

当用户关闭应用程序时,应用程序必须删除临时文件。

我们希望用户总是点击退出按钮,因为这样会调用关闭方法(删除临时文件)。

然而,如果用户通过点击窗口的X按钮或其他方式关闭应用程序,临时文件将不会被删除。

The class in Listing 16.3 offers a solution to this. It modifies the code in Listing 16.2 by providing a shutdown hook. The shutdown hook class is declared as an inner class so that it has access to all the main class's methods. In Listing 16.3, the shutdown hook's run method calls the shutdown method, guaranteeing that this method will be invoked when the virtual machine shuts down.

列表16.3中的类提供了一个解决方案。

它通过提供一个关闭挂钩来修改列表16.2中的代码。

关闭挂钩类被声明为内部类,以便可以访问所有主类的方法。

在列表16.3中,关闭挂钩的run方法调用关闭方法,确保在虚拟机关闭时将调用此方法。

Listing 16.3: Using a shutdown hook in the Swing application

列表16.3:在Swing应用程序中使用关闭挂钩


package ex16.pyrmont.shutdownhook;
        import java.awt.*;
        import javax.swing.*;
        import java.awt.event.*;
        import java.io.File;
        import java.io.IOException;
public class MySwingAppWithShutdownHook extends JFrame {
    JButton exitButton = new JButton();
    JTextArea jTextArea1 = new JTextArea();
    String dir = System.getProperty("user.dir");
    String filename = "temp.txt";
    public MySwingAppWithShutdownHook() {
        exitButton.setText("Exit");
        exitButton.setBounds(new Rectangle(304, 248, 76, 37));
        exitButton.addActionListener(new java.awt.event.ActionListener() {
            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() {
        // add shutdown hook
        MyShutdownHook shutdownHook = new MyShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);
        // create a temp file
        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.");
        }
    }
    private void shutdown() {
        // delete the temp file
        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);
    }
    public static void main(String[] args) {
        MySwingAppWithShutdownHook mySwingApp = new
                MySwingAppWithShutdownHook();
    }
    private class MyShutdownHook extends Thread {
        public void run() {
            shutdown();
        }
    }
}

Pay special attention to the initialize method in the class shown in Listing 16.3. The first thing it does is create an instance of the inner class MyShutdownHook, which extends a java.lang.Thread:

请特别注意清单 16.3 中所示类的初始化方法。它要做的第一件事就是创建一个内部类 MyShutdownHook 的实例,该类扩展了一个 java.lang.Thread.MyShutdownHook 类:

// add shutdown hook
 MyShutdownHook shutdownHook = new MyShutdownHook();

Once you get an instance of MyShutdownHook, you pass it to the addShutDownHook method of the Runtime, as in the following line of code:

一旦获得 MyShutdownHook 的实例,就可以将其传递给 Runtime 的 addShutDownHook 方法,如下行代码所示:

Runtime.getRuntime().addShutdownHook(shutdownHook);

The rest of the initialize method is similar to the initialize method in the class in Listing 16.2. It creates a temporary file and prints the string Creating temporary file.

初始化方法的其余部分与清单 16.2 中类的初始化方法类似。

它创建了一个临时文件,并打印了创建临时文件的字符串。

   // create a temp file  
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.");  
}  
}

Now, start the small application given in Listing 16.3. Check that the temporary file is always deleted even if you abruptly shut down the application.

现在,启动清单 16.3 中的小程序。

检查临时文件是否始终被删除,即使突然关闭程序也是如此。

Shutdown Hook in Tomcat

As you may expect, Tomcat equips itself with a shutdown hook. You can find it in the org.apache.catalina.startup.Catalina class, the class responsible for starting a Server object that manages other components. An inner class named CatalinaShutdownHook (See Listing 16.4) extends java.lang.Thread and provides the implementation of the run method that calls the stop method of the Server object.

正如你所期望的那样,Tomcat装备了一个关闭钩子。

你可以在org.apache.catalina.startup.Catalina类中找到它,这个类负责启动一个管理其他组件的Server对象。

一个名为CatalinaShutdownHook的内部类(见代码清单16.4)继承自java.lang.Thread,并提供了run方法的实现,该方法调用Server对象的stop方法。

Listing 16.4: Catalina shutdown hook

代码清单16.4: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);  
                }  
            }  
        }  
    }  
}

This shutdown hook is instantiated and added to the Runtime at one stage when the Catalina instance is started. You will learn more about the Catalina class in Chapter 17.

这个关闭挂钩是在Catalina实例启动时的某个阶段实例化并添加到运行时的。

在第17章中,你将了解更多关于Catalina类的内容。

Summary

Sometimes we want our application to run some clean up code prior to shutting down. However, it is impossible to rely on the user to always quit properly. The shutdown hook described in this chapter offers a solution that guarantees that the clean up code is run regardless how the user closes the application

有时我们希望在关闭应用程序之前运行一些清理代码。然而,无法依赖用户始终正确退出。

本章介绍的关闭挂钩提供了一种解决方案,无论用户如何关闭应用程序,都可以保证清理代码被执行。


Xander
198 声望51 粉丝