How tomcat works——16 关闭钩子

概述

在很多环境下,当用户在关闭应用程序时我们需要做一些清理工作。问题在于,用户并不是经常的按照预推荐流程来退出。例如,在 Tomcat 部署中通过初始化一个服务器(Server)并调用它的 start()方法来启动一个 servlet 容器,该方法又调用其它组件的start()方法。正常情况下,可以通过一个关闭命令来让服务器关闭所有组件,犹如第14 章中介绍。如果突发地关闭程序,如在程序还在运行时关闭其控制台,可能会发生一些想不到的事情。

幸运的是,Java 为程序员提供了一种优雅的方式,保证清理代码总会执行。本章将会介绍如何使用一个关闭钩子(shutdown hook)来保证清理代码一定会被执行,不管用户怎么关闭应用。

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

•应用程序正常退出如System.exit()方法被调用或最后一个非守护线程退出时。
•用户突然强制终止虚拟机,例如键入 CTRL+C 或在关闭 Java程序之前从系统注销。

幸运的是,当关闭的时候,虚拟机会执行以下两个步骤:
1.虚拟机启动所有已注册的关闭钩子。关闭钩子是通过Runtime 注册的线程。所有关闭钩子会被同时执行直到完成;
2.如果需要,虚拟机则调用所有的未被调用的 finalizers。

在本章中,我们主要介绍第一个步骤,因为它允许程序员告诉虚拟机在程序中执行一些清理代码。一个关闭钩子是 java.lang.Thread 类的子类,可以如下创建一个关闭钩子:
•写一个类继承 Thread 类;
•实现这个类的run()方法。该方法是应用程序被关闭时要执行的代码,无论是正常还是非正常关闭;
•在我们的应用程序中,初始化一个关闭钩子;
•在当前的 Runtime上使用 addShutdownHook()方法来注册该关闭钩子。

你可能已经注意到,我们并没有把关闭钩子作为独立线程启动。虚拟机会在它的关闭步骤中启动该线程。

Listing16.1 提供了一简单关闭钩子例子。例子中仅仅打印出一些语句到控制台上,实际上,我们可以在其中插入任何想要执行代码。

Listing 16.1: Using Shutdown Hook

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");
    }
}

在初始化ShutdownHookDemo 对象后,调用 start()方法。start()方法创建一个关闭钩子并在当前 Runtime 中注册:

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

然后该程序等待用户键入回车键:

System.in.read();

用户键入 Enter 键后,应用程序退出。但是虚拟机会运行关闭钩子,看到的效果就是打印出语句“Shutting down”。

16.1 一个关闭钩子实例

另一个例子,考虑一个简单的 Swing 应用程序,名为MySwingApp,界面如图16.1。该应用程序启动时创建一个临时文件,在它关闭时必须要删除此临时文件。
这里写图片描述
图16.1: A Swing application

该类的代码如 Listing16.2 所示:

Listing 16.2: A simple Swing application

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();
  }
}

当程序启动时,调用initialize()方法创建一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.");
    }
  }

当用户关闭该应用程序时,程序必须删除该临时文件。我们希望用户总是点击 Exit 按钮,因为这样在 shutdown()方法中就可以总是删除临时文件。但是,当用户点击窗口右上方X按钮非正常退出时,临时文件却不会被删除。

Listing16.3 所示的类提供了一种解决方案。修改Listing16.2代码提供了一个关闭钩子。该关闭钩子作为一个内部类来声明,以便于它可以访问主类的所有方法。在 Listing16.3中,关闭钩子的 run()方法调用了shutdown()方法,保证了当Java虚拟机关闭时该方法一定会被执行。

Listing 16.3: Using a shutdown hook in the Swing application

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();
        }
    }
}

注意该类的 initialize()方法,它首先就是创建一MyshutdownHook内部类对象:

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

我们一旦获得MyShutdownHook的实例,就可以通过Runtime 的 addShutDownHook()方法注册钩子,如下:

Runtime.getRuntime().addShutdownHook(shutdownHook);

initialize()方法的其它部分就跟 Listing16.2 所示的相同。它创建临时文件并打印出“Creating temporary file”:

// 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.");
}

接下来启动该应用程序,检查是否是不管怎么关闭应用,临时文件都总是会被删除。

16.2 Tomcat中关闭钩子

如你所料,Tomcat 也有自己的关闭钩子。我们可以在org.apache.catalina.startup.Catalina 类中找到它,该类负责启动服务器对象以及管理其它组件。在该类中有一个内部类 CatalinaShutdownHook 继承了java.lang.Thread 类(如Listing 16.4所示),在该类的run()方法中调用了服务器的 stop()方法。

Listing 16.4: Catalina shutdown hook

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 实例启动时被初始化并添加到 Runtime 中。我们可以在第 17 章学习到Catalina更多的细节。

16.3 小结

有时我们希望应用程序在关闭之前运行一些清理代码。然而,不可能依靠用户总是正常退出。本章提供了“关闭钩子”这种解决方案,保证了无论用户如何关闭应用程序,清理代码都会执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值