Внезапное закрытие Java-приложения: как этого избежать?
Budi Kurniawan
Во многих случаях у программистов возникает необходимость выполнить некоторые действия в момент, когда пользователь заканчивает работу с приложением. Но проблема состоит в том, что пользователи не всегда пользуются рекомендованными и правильными способами выхода из приложения. Java предоставляет элегантный подход к выполнению какого-либо кода в середине процесса выгрузки процесса вашего приложения, таким образом гарантируя, что этот код, который, например, занимается какими-либо "очистительными" операциями, будет обязательно выполнен. Эта статья рассказывает о том, каким образом можно вешать обработчик прерывания работы приложения для гарантированного выполнения завершающего кода независимо от того, каким образом пользователь завершил работу с вашим приложением. Очень часто бывает нужно выполнять какие-то операции по завершению приложения. Например, когда вы пишете текстовый редактор с использованием Swing, и это ваше приложение создает временный файл при начале своей работы. Временный файл должен быть удален, как только пользователь закроет ваше приложение. Если же вы пишете приложение, состоящее из множества сервлетов, встраиваемых в сервлет-контейнер (например, Tomcat или Jetty), то вы должны вызывать метод destroy для каждого из загруженных вами сервлетов до того, как завершится работа приложения. Во многих случаях вы надеетесь на то, что пользователь закроет приложение приемлемым для вас способом. Например, в первом случае вы можете предоставить ему компонент JButton, после клика на который выполняются необходимые завершающие операции и осуществляется непосредственно выход из приложения. Как альтернативный вариант вы можете повесить обработчик события окна, который бы обрабатывал событие windowClosing. Tomcat же использует специальный batch-файл, который может быть выполнен при правильном завершении работы с приложением. Однако хорошо известно, что пользователи далеко не так часто корректно завершают работу с приложениями. Они могут делать с приложениями все что пожелают. Помимо этого, пользователь может просто-напросто закрыть консоль или завершить свой сеанс работы с операционной системой, оставив при этом ваше приложение незакрытым. В Java виртуальная машина завершает работу в двух случаях: во-первых, когда из приложения вышли нормальным способом, т.е. был вызван метод System.exit, или же когда остался последний поток, не являющийся демоном. Во-вторых, когда пользователь внезапно прерывает работу виртуальной машины, например, нажимая комбинацию клавиш Ctrl+C или же выходя из системы, не закрыв предварительно работающее Java-приложение. К счастью, виртуальная машина следует следующей двухфазной последовательности действий, прежде чем выгрузить себя: 1. Виртуальная машина запускает все зарегистрированные shutdown-ловушки, если таковые были установлены. Shutdown-ловушки - это нити (threads), которые регистрируются с помощью класса Runtime. Все эти ловушки будут запущены и будут работать параллельно до тех пор, пока все они не завершат своей работы. 2. Виртуальная машина вызывает все определенные fina-lize-операции (если есть подходящие). В этой статье мы рассмотрим первый пункт, поскольку он позволяет программисту озадачить виртуальную Java-машину выполнением необходимых операций по завершению приложения. Shutdown-ловушки - это просто экземпляры классов-наследников класса Thread. Чтобы создать такую ловушку, нужно выполнить следующую последовательность действий: 1. Описать класс, наследующий класс Thread. 2. Осуществить реализацию метода run этого нового класса. Этот метод содержит код, который и будет выполняться для завершения работы виртуальной машины вне зависимости от того, нормально или нет было завершено приложение. 3. Связать класс shutdown-ловушки с вашим приложением. 4. Зарегистрировать ловушку с помощью метода addShutdownHook текущего экземпляра класса Runtime. Как вы уже могли заметить, вам не нужно запускать только что созданную нить ловушки, как вы бы запускали другой класс, унаследовавший Thread. Забота о запуске этой нити ложится на виртуальную машину, которая, подойдя к выполнению своей shutdown-последовательности, запустит все зарегистрированные нити ловушек. Код Листинга 1 представляет простой класс Shutdown HookDemo и подкласс класса 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 ShutdownHook Demo public void start() System.out.println ("Demo"); ShutdownHook shutdown Hook = 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.
Листинг 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.getPro perty("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.Action Listener() public void actionPerfor- med(ActionEvent e) exitButton_actionPerformed(e); this.getContentPane().set Layout(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.setDefaultCloseOpera-tion(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("Crea-ting 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_action Performed(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("Crea-ting 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 MySwingAppWith ShutdownHook extends JFrame JButton exitButton = new JButton(); JTextArea jTextArea1 = new JTextArea(); String dir = System.getPro perty("user.dir"); String filename = "temp. txt"; public MySwingAppWithShut-downHook() exitButton.setText("Exit"); exitButton.setBounds(new Rectangle(304, 248, 76, 37); exitButton.addActionListener(new java.awt.event.Ac-tionListener() public void actionPer-formed(ActionEvent e) exitButton_actionPerformed(e); this.getContentPane().set Layout(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.setDefaultCloseOpera-tion(EXIT_ON_CLOSE); this.setBounds(0,0, 400, 330); this.setVisible(true); initialize(); private void initialize() добавление shutdown-ловушки MyShutdownHook shutdown Hook = new MyShutdownHook(); Runtime.getRuntime().addShutdownHook(shutdownHook); создание временного файла File file = new File(dir, filename); try System.out.println("Crea-ting 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("Dele-ting temporary file."); file.delete(); void exitButton_action Performed(ActionEvent e) shutdown(); System.exit(0); public static void main (String[] args) MySwingAppWithShutdown Hook mySwingApp = new My SwingAppWithShutdownHook(); private class MyShutdown Hook extends Thread public void run() shutdown();
Обратите внимание на метод initialize. Первое, что он делает - создает экземпляр внутреннего класса MyShut-downHook, который наследует класс Thread. MyShutdownHook shutdown Hook = new MyShutdownHook(); Теперь, получив экземпляр класса MyShutdownHook, мы регистрируем его в Runtime с помощью метода addShut downHook: Runtime.getRuntime().add ShutdownHook(shutdownHook); Оставшаяся часть метода initialize в точности соответствует такому же методу из Листинга 2. В этой части создается временный файл и выводится строчка Creating temporary file. Теперь попробуйте запустить это небольшое Swing-приложение. Убедитесь в том, что временный файл удаляется в любом случае, каким бы способом вы ни закрыли приложение. Shutdown-ловушки, которые мы рассмотрели в этой статье, являются фактически единственным правильным решением проблемы выполнения какого-либо кода по завершению приложения. А поскольку нельзя быть уверенным в том, каким образом пользователь на этот раз закроет ваше приложение, их использование значительно облегчит вам жизнь, гарантируя выполнение установленных вами правил.
Страница сайта http://www.silicontaiga.ru
Оригинал находится по адресу http://www.silicontaiga.ru/home.asp?artId=5494
|