Как хранить настройки Java-программ?
Как хранить настройки Java-программ?
Этот вопрос задает себе каждый разработчик любого серьезного Java-приложения. Классическим решением этой задачи является использование класса Properties. Этот класс поддерживает коллекции свойств (Properties) вида ключ/значение, где ключи и значения являются строками. Наличие методов сохранения и чтения коллекций в файлах упрощает организацию физического хранения данных. Несмотря на удобство и простоту использования данного класса, хочется обратить ваше внимание на то, что класс Properties является наследником устаревшего класса Hashtable [1]. Именно поэтому рекомендуется использовать класс HashMap, который является аналогом Hashtable.
Для доступа к настройкам программы создадим класс AppSettings. Причем в блоке инициализации статических членов класса будет вызываться Private-конструктор данного класса. Причем при первом вызове из основного кода программы создается единственный экземпляр данного объекта в статическую переменную SINGLETON. Реализующий эту методику код имеет вид:
Таким образом, к единственному экземпляру данного класса можно получить доступ из любого модуля программы, что напоминает глобальные переменные в других языках программирования. Коллекция HashMap позволяет хранить различные объекты, но мы пока ограничимся только строковыми параметрами. Методы доступа к данным могут иметь вид:
Раз уж мы отказались от использования класса Properties, то изменим формат хранения объектов коллекции на более прогрессивный XML-формат. Структура XML-файла будет иметь вид:
Данная структура позволит нам в будущем хранить не только значения вида ключ/значение, но и другие типы объектов. Пока же ограничимся строками аналогично классу Properties. Код построения DOM-дерева заданной структуры и трансформации в XML-файл будет иметь вид:
Класс сериализации DOM-дерева позаимствован в [2]. Для обратной операции чтения данных в коллекцию создадим следующий метод:
А пример вызова методов класса AppSettings в теле программы показан в следующем участке кода:
Как видно из приведенного кода, все достаточно тривиально. Просто вызываем нужный метод класса AppSettings, создавать экземпляр которого не требуется. Для удобства использования можно реализовать дополнительные методы извлечения часто используемых типов данных, например, getString().
Код демонстрационного проекта можно скачать по адресу:
http://berdachuk.at.tut.by/downloads/appsettingsdemo.zip .
Литература:
1. Арнольд Кен, Гослинг Джеймс, Холмс Дэвид. "Язык программирования Java". Пер. с англ. — М.: Издательский дом "Вильямс", 2001. — 624 с. ISBN 5-8459-0215-0 (рус.).
2. Brett McLaughlin "Java& XML, 2nd Edition" Publisher: O'Reilly. Second Edition September 2001. ISBN: 0-596-00197-5, 528 pages.
Сергей Бердачук,
Berdachuk@tut.by
Этот вопрос задает себе каждый разработчик любого серьезного Java-приложения. Классическим решением этой задачи является использование класса Properties. Этот класс поддерживает коллекции свойств (Properties) вида ключ/значение, где ключи и значения являются строками. Наличие методов сохранения и чтения коллекций в файлах упрощает организацию физического хранения данных. Несмотря на удобство и простоту использования данного класса, хочется обратить ваше внимание на то, что класс Properties является наследником устаревшего класса Hashtable [1]. Именно поэтому рекомендуется использовать класс HashMap, который является аналогом Hashtable.
Для доступа к настройкам программы создадим класс AppSettings. Причем в блоке инициализации статических членов класса будет вызываться Private-конструктор данного класса. Причем при первом вызове из основного кода программы создается единственный экземпляр данного объекта в статическую переменную SINGLETON. Реализующий эту методику код имеет вид:
public class AppSettings { private AppSettings() { fHashMap = new HashMap(); } . . . private H ashMap fHashMap; private static AppSettings SINGLETON; static { SINGLETON = new AppSettings(); } }
Таким образом, к единственному экземпляру данного класса можно получить доступ из любого модуля программы, что напоминает глобальные переменные в других языках программирования. Коллекция HashMap позволяет хранить различные объекты, но мы пока ограничимся только строковыми параметрами. Методы доступа к данным могут иметь вид:
// Извлечение объекта из коллекции public static Object get(String key) {return SINGLETON.fHas hMap.get(key); } // Извлечение объекта из коллекции // при отсутствии данных возвращается значение п о умолчанию public static Object get(String key, Object deflt) {Object obj = SINGLETON.fHashMap.get( key);if (obj == null) { return deflt;} else { return obj;} } // Для упрощения извлечения данных типа int public static int getInt(String key, int deflt) {Object obj = SINGLETON.fHashMap.get(key);if (o bj == null) { return deflt;} else { return new Integer((String) obj).intValue();} } // Добавление об ъекта в коллекцию public static void put(String key, Object data) {//prevent null values. Hasmap all ow themif (data == null) { throw new IllegalArgumentException();} else { SINGLETON.fHashMap.put(key, data);} }
Раз уж мы отказались от использования класса Properties, то изменим формат хранения объектов коллекции на более прогрессивный XML-формат. Структура XML-файла будет иметь вид:
<?xml version = '1.0'?> <app-settings><properties><property key="Mai nFrame.height">319</property><property key="MainFrame.width">424</p roperty><property key="LookAndFeel">com.sun.java.swing.plaf.motif.MotifLookAndFee l</property></properties> </app-settings>
Данная структура позволит нам в будущем хранить не только значения вида ключ/значение, но и другие типы объектов. Пока же ограничимся строками аналогично классу Properties. Код построения DOM-дерева заданной структуры и трансформации в XML-файл будет иметь вид:
public static boolean save(File file) throws Exception {// Создаем новое DOM-деревоDOMImplemen tation domImpl = new DOMImplementationImpl();Document doc = domImpl.createDocument(null, "app-s ettings", null);Element root = doc.getDocumentElement();Element propertiesElement = doc.createE lement("properties");root.appendChild(propertiesElement);Set set = SINGLETON.fHashMap.keyS et();if (set != null) { for (Iterator iterator = set.iterator(); iterator.hasNext(); ) { String key = iterator.next().toString(); Element propertyElement = doc.createElement("property"); pro pertyElement.setAttribute("key", key); Text nameText = doc.createTextNode(get(key).toStrin g()); propertyElement.appendChild((Node) nameText); propertiesElement.appendChild(propertyElement); }}// Сериализируем DOM-дерево в файлDOMSerializer serializer = new DOMSerializer();serializer.serial ize(doc, file);return true; }
Класс сериализации DOM-дерева позаимствован в [2]. Для обратной операции чтения данных в коллекцию создадим следующий метод:
public static boolean load(File file) throws Exception {DocumentBuilderFactory factory = Docum entBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();Document doc = builder.parse(file);if (doc == null) { throw new NullPointerException();}NodeList propertiesNL = d oc.getDocumentElement().getChildNodes();if (propertiesNL != null) { for (int i = 0; (i < properti esNL.getLength()); i++) { if (propertiesNL.item(i).getNodeName().equals("properties")) { N odeList propertyList = propertiesNL.item(i).getChildNodes(); for (int j = 0; j < propertyList.get Length(); j++) { NamedNodeMap attributes = propertyList.item(j).getAttributes(); if (attributes != n ull) { Node n = attributes.getNamedItem("key"); NodeList childs = propertyList.item(j).get ChildNodes(); if (childs != null) { for (int k = 0; k < childs.getLength(); k++) { if (childs.ite m(k).getNodeType() == Node.TEXT_NODE) { put(n.getNodeValue(), childs.item(k).getNodeValue()); } } } } } } } return true;} else { return false;} }
А пример вызова методов класса AppSettings в теле программы показан в следующем участке кода:
File file = new File(propDir, "settings.xml"); try {AppSettings.clear();AppSettings. load(file);String lnfName = UIManager.getLookAndFeel().getClass().getName();if (AppSettings.get(LF_K EY, lnfName) != lnfName) { UIManager.setLookAndFeel( (String) AppSettings.get(LF_KEY, lnfName)); Swi ngUtilities.updateComponentTreeUI(MainFrame.this);}this.setSize(new Dimension( AppSettings.getInt(WI DTH_KEY, getWidth()), AppSettings.getInt(HEIGHT_KEY, getHeight()) )); } catch (Exception e) {e.print StackTrace(); }
Как видно из приведенного кода, все достаточно тривиально. Просто вызываем нужный метод класса AppSettings, создавать экземпляр которого не требуется. Для удобства использования можно реализовать дополнительные методы извлечения часто используемых типов данных, например, getString().
Код демонстрационного проекта можно скачать по адресу:
http://berdachuk.at.tut.by/downloads/appsettingsdemo.zip .
Литература:
1. Арнольд Кен, Гослинг Джеймс, Холмс Дэвид. "Язык программирования Java". Пер. с англ. — М.: Издательский дом "Вильямс", 2001. — 624 с. ISBN 5-8459-0215-0 (рус.).
2. Brett McLaughlin "Java& XML, 2nd Edition" Publisher: O'Reilly. Second Edition September 2001. ISBN: 0-596-00197-5, 528 pages.
Сергей Бердачук,
Berdachuk@tut.by
Компьютерная газета. Статья была опубликована в номере 14 за 2004 год в рубрике программирование :: java