在很多实际应用环境中, 当用户关了应用程序时, 需要做一些善后清理工作, 但问题是, 用户有时并不会按照推荐的方法关闭应用程序, 很有可能不做清理工作, 例如在 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" 输出到控制台上, 但是可以插入想在应用程序关闭之前的任何代码.
- package myex16.pyrmont.shutdownhook;
- import java.io.IOException;
- /**
- * <p>
- * <b>Title:ShutdownHookDemo.java</b>
- * </p>
- * <p>
- * Copyright:ChenDong 2018
- * </p>
- * <p>
- * Company: 仅学习时使用
- * </p>
- * <p>
- * 类功能描述: 演示 关闭钩子 的简单实用
- * </p>
- *
- * @author 陈东
- * @date 2018 年 12 月 24 日 下午 8:01:14
- * @version 1.0
- */
- public class ShutdownHookDemo {
- public void start() {
- System.out.println("Demo start");
- // 创建关闭钩子 就是线程
- ShutdownHook shutdownHook = new ShutdownHook();
- // 像虚拟机中注册关闭钩子
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- }
- /**
- *
- * <p>
- * Title: main
- * </p>
- *
- * @date 2018 年 12 月 24 日 下午 8:01:15
- *
- * <p>
- * 功能描述:
- * </p>
- *
- * @param args
- *
- */
- public static void main(String[] args) {
- ShutdownHookDemo demo = new ShutdownHookDemo();
- demo.start();
- try {
- // 等待输入 这样 线程就不会走完 然后只要随便输入东西 就会 走完流程 测试在线程运行完之后 虚拟机启动我们注册的关闭钩子 并运行
- System.in.read();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- class ShutdownHook extends Thread {
- public void run() {
- System.out.println("Shutting down");
- }
- }
运行结果
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, 效果如图
该应用程序会在它启动时创建一个临时文件, 并在关闭时删除该临时文件.
- 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() {
- // 创建一个 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
- *
- */
- 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);
- }
- }
在实例化这个类时, 应用程序会调用其 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 类中,
有时候, 应用程序在关闭之前应该执行一些代码清理工作, 但是你不能价设定用户总是正常退出, 那么这次介绍的关闭钩子提供了一种解决方案, 确保无论用户如何关闭应用程序, 清理代码总是能得到执行.
来源: https://www.cnblogs.com/ChenD/p/10171001.html