Внезапное закрытие Java-приложения: как избежать?

Автор: content Понедельник, Апрель 9th, 2012 Нет комментариев

Рубрика: Язык Java

Во многих случаях, у программистов возникает необходимость выполнить некоторые действия в момент, когда пользователь заканчивает работу с приложением. Но проблема состоит в том, что пользователи не всегда пользуются рекомендованными и правильными способами выхода из приложения. Java предоставляет элегантный подход к выполнению какого-либо кода в середине процесса выгрузки процесса вашего приложения, таким образом гарантируя, что этот код, который, например, занимается какими-либо “очистительными” операциями, будет обязательно выполнен. Эта статья рассказывает о том, каким образом можно вешать обработчик прерывания работы приложения для гарантированного выполнения завершающего кода, независимо от того, каким образом пользователь завершил работу с вашим приложением.

Очень часто бывает нужно выполнять какие-то операции по завершению приложения. Например, когда вы пишите текстовый редактор с использованием Swing, и это ваше приложение создает временный файл при начале своей работы. Временный файл должен быть удален, как только пользователь закроет ваше приложение. Если же вы пишите приложение, состоящее из множества сервлетов, встраиваемых в сервлет-контейнер (например, Tomcat или Jetty), то вы должны вызывать метод destroy для каждого из загруженных вами сервлетов до того, как завершится работа приложения.

Во многих случаях, вы надеетесь на то, что пользователь закроет приложение “нормальным” для вас способом. Например, в первом случае вы можете предоставить ему компонент JButton, после клика на который выполняются необходимые завершающие операции и осуществляется непосредственно выход из приложения. Как альтернативный вариант вы можете повесить обработчик события окна, который бы обрабатывал событие windowClosing. Tomcat же использует специальный batch-файл, который может быть выполнен при правильном завершении работы с приложением. Однако, хорошо известно, что пользователи далеко не так часто корректно завершают работу с приложениями. Они могут делать с приложениями все, что пожелают. Помимо этого пользователь может просто-напросто закрыть консоль или завершить свой сеанс работы с операционной системой, оставив при этом ваше приложение незакрытым.

В Java, “виртуальная” машина завершает работу в двух случаях: во-первых, когда из приложения вышли нормальным способом, т.е. был вызван метод System.exit или же когда остался последний поток не являющийся демоном. Во-вторых, когда пользователь внезапно прерывает работу “виртуальной” машины, например, нажимая комбинацию клавиш Ctrl+C или же выходя из системы, не закрыв предварительно работающее Java-приложение.

К счастью, виртуальная машина следует следующей двухфазной последовательности действий, прежде чем выгрузить себя:

Виртуальная машина запускает все зарегистрированные shutdown-ловушки, если таковые были установлены. Shutdown-ловушки – это нити (threads), которые регистрируются с помощью класса Runtime. Все эти ловушки будут запущены и будут работать параллельно до тех пор, пока все они не завершат своей работы.
Виртуальная машина вызывает все определенные finalize-операции (если есть подходящие).

В этой статье мы рассмотрим первый пункт, поскольку он позволяет программисту озадачить “виртуальную” Java-машину выполнением необходимых операций по завершению приложения. Shutdown-ловушки – это просто экземпляры классов-наследников класса Thread. Чтобы создать такую ловушку нужно выполнить следующую последовательность действий:

Описать класс, наследующий класс Thread.
Осуществить реализацию метода run этого нового класса. Этот метод содержит код, который и будет выполняться по завершению работы “виртуальной” машины вне зависимости от того, нормально или нет было завершено приложение.
Связать класс shutdown-ловушки с вашим приложением.
Зарегистрировать ловушку с помощью метода addShutdownHook текущего экземпляра класса Runtime.

Как вы уже могли заметить, вам не нужно запускать только что созданную нить ловушки, как вы бы запускали другой класс, унаследовавший Thread. Забота о запуске этой нити ложится на “виртуальную” машину, которая, подойдя к выполнению своей shutdown-последовательности, запустит все зарегистрированные нити ловушек.

Код Листинга 1 представляет простой класс ShutdownHookDemo и подкласс класса Thread – ShutdownHook. Учтите, что метод run класса ShutdownHook просто выводит строку “Shutting down” на консоль. Конечно, вы можете вставить абсолютно любой код, который вам необходимо выполнить во время завершения вашего приложения.

После запуска public-класса, вызывается метод start. Метод start создает shutdown-ловушку и регистрирует ее в текущем экземпляре Runtime-клас

ShutdownHook shutdownHook = new ShutdownHook();

Runtime.getRuntime().addShutdownHook(shutdownHook);

После этого программа ждет нажатия пользователем клавиши Enter.

System.in.read();

Когда пользователь нажимает Enter, осуществляется выход их программы. Однако перед выходом “виртуальная” машина запускает зарегистрированную shutdown-ловушку, которая в свою очередь печатает строчку “Shutting down”.
Листинг 1

package test;

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

В качестве следующего примера мы рассмотрим простое Swing-приложение, главный класс которого называется MySwingApp. Это приложение создает временный файл при запуске. Когда оно закрывается, файл должен быть удален. Код этого приложения приведен в Листинге 2.

Listing 2.

package test;

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() {
// создание временного файла
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() {
// удаление временного файла
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() {
// создание временного файла
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 окна приложения или каким-либо другим способом.

В Листинге 3 приведен вариант этого же приложения, который разрешает эту проблему. Новый вариант приложения модифицирует код из Листинга 2, устанавливая shutdown-ловушку. Класс этой ловушки определяется как внутренний класс основного класса приложения. Таким образом, он получает доступ ко всем полям и методам основного класса. В Листинге 3, метод run класса-ловушки просто вызывает метод shutdown основного класса. Этим гарантируется его вызов по завершению приложения.
Листинг 3.

package test;

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() {
// добавление shutdown-ловушки
MyShutdownHook shutdownHook = new MyShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
// создание временного файла
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() {
// удаление временного файла
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, который наследует класс Thread.

MyShutdownHook shutdownHook = new MyShutdownHook();

Теперь, получив экземпляр класса MyShutdownHook, мы регистрируем его в Runtime с помощью метода addShutdownHook:

Runtime.getRuntime().addShutdownHook(shutdownHook);

Оставшаяся часть метода initialize в точности соответствует такому же методу из Листинга 2. В этой части создается временный файл и выводится строчка «Creating temporary file».

Теперь попробуйте запустить это небольшое Swing-приложение. Убедитесь в том, что временный файл удаляется в любом случае, каким бы способом вы не закрыли приложение.

Shutdown-ловушки, которые мы рассмотрели в этой статье, являются фактически единственным правильным решением проблемы выполнения какого-либо кода по завершению приложения. А поскольку нельзя быть уверенным в том, каким образом пользователь на этот раз закроет ваше приложение – их использование значительно облегчит вам жизнь, гарантируя выполнение установленных вами правил завершения программы.

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты