Java: Конфигурация программ

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

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

Зачем нужно конфигурирование?

Профессиональным программистам этот вопрос покажется странным. У начинающих же часто наблюдается явное непонимание важности этой возможности. При этом получается программа, похожая на каменную глыбу с высечеными на ней надписями — если захочется изменить надпись, то придётся делать новый камень.

Есть и другая крайность — когда практически всё выносится в настройки. Такие программы напоминают разлитую жидкость, а чтобы заставить её работать надо прочитать талмуд описания и настроить несколько сотен параметров, к тому же часто взаимосвязанных противоестественным образом.

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

Вот типичные примеры данных, которые часто стоит вынести в настройки:

Всевозможные каталоги. Например — пути до файлов данных, каталоги импорта/экспорта.
Сетевые настройки. Имена серверов, IP-адреса, порты, имена и пароли для автоматического доступа.
Настройки баз данных. Имена JDBC-драйверов, URL базы данных, SQL-запросы, зависимые от используемой БД.
Настройки внешнего вида. Настройки Swing-овского Look & Feel-а, используемые шрифты, размеры, цвета, настройки горячих клавиш.
Прочее… Любые другие вещи, которые могут менятся от пользователя к пользователю.

Например, довольно часто встречаемая ситуация — настройка соединения с БД. Начинающие программисты часто пишут нечто подобное:

Class.forName(«sun.jdbc.odbc.JdbcOdbcDriver»);

Connection con = DriverManager.
getConnection(«jdbc:odbc:MyDatabase»,user,password);

Таким образом программа привязывается к конкретному JDBC драйверу. Использовать другой драйвер, например заменить мост на RMI-прокси или, в случае Oracle, OCI на Thin без перекомпиляции уже нельзя.
Способы хранения настроек.

В объектном программировании всё представляется в виде объектов. Настройки лучше всего при этом рассматривать как свойства определённых объектов, которые хранятся в файлах конфигураций. То, каким образом эти настройки считываются и записываются тесно взаимосвязано с форматом файлов и выбраной стратегией администрирования. Рассмотрим идеальный вариант:

Настраиваемый объект не должен содержать знаний о формате файлов и способе чтения/записи. Это позволило бы, в случае необходимости, заменить один способ другим.
Большинство настроек должны выполняться при помощи программы (подпункт меню или отдельная программа настройки). Это сильно облегчает жизнь человека, который занимается администрированием. У большинства «юниксоидов» это может вызвать непонимание :-) , но редактированием текстовых файлов в современном мире во многих случаях не обойтись.
Должно быть установлено разумное умолчание для отсутствующих параметров. Другими словами — необходимо, чтобы большинству пользователей для запуска программы нужно было бы сделать минимум настроек. Как правило это оставляет благоприятное первое впечатление о программе, а часто именно оно — самое важное.

К сожалению этот идеальный вариант довольно трудно сделать на практике. Первое требование предполагает разработку универсального механизма сохранения объектов. Такие системы уже есть готовые, но часто они не подходят по тем или иным параметрам. Разработать же самому такую систему — далеко не каждому под силу.

Второе требование подразумевает, что для каждого объекта пишется своя панель (или диалог) для редактирования настроек. В случае большого количества объектов стоит попробовать использовать универсальные механизмы. Один из вариантов — использование стандарта JavaBeans. Этот стандарт разрабатывался для визуальных систем программирования, но, из-за сходства решаемых задач, также хорошо подходит для универсального конфигурирования. Но это тоже не самая простая задача, поэтому часто разумно предусмотреть возможность альтернативного варианта конфигурирования для пожарных случаев — например, при помощи обычных текстовых редакторов в случае использования текстовых форматов файлов.

Разумное же умолчание для параметров часто просто невозможно представить. Например, что поставить в качестве имени SMTP-сервера? В случае Unix-систем можно попробовать поставить localhost, но для Windows-мира это редко кому подойдёт.

Рассмотрим наиболее распространённые варианты:
Ini-файлы.

Ini-файлы — это был самый распространённый вариант в эпоху Windows 3.x. Сейчас в виндовых программах он стал вытесняться хранением настроек в реестре. Тем не менее ini — это один из простейших вариантов хранения настроек. К сожалению довольно часто эта простота заставляет прибегать к различно рода ухищрениям. Пример типичного ini-файла:

[Communication]
InputDir=INPUT
OutputDir=OUTPUT
ArchDir=ARHIV
TransferPath = a:\cour

[Warning]
NoReceived=No

[Addons]
Numb = 3
MenuName1 = ~N~orton
ProgName1 = mousesav c:\command.com /c nc
MenuName2 = Win — ~Б~локнот
ProgName2 = notepad
MenuName3 = Импорт из формата АБ «Инкомбанк»
ProgName3 = incom.bat

В Java нет стандартного класса для чтения ini-файлов, но это не проблема. Т.к. формат очень прост, его легко сделать самому:

import java.io.*;
import java.util.*;

public class INIFile
{
Properties iniProperty = new Properties();

public INIFile(File f) { this( f.getPath() ); }
public INIFile(String fname) throws IOException
{ loadFile( fname ); }

private void loadFile( String fname ) throws IOException
{
BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(fname)));

try
{
String section = «»;
String line;

while( (line = br.readLine())!=null )
{
if( line.startsWith(«;») ) continue;
if( line.startsWith(«[") )
{
section = line.substring(1,line.lastIndexOf("]«)).trim();
continue;
}
addProperty(section,line);
}
}
finally { br.close(); }
}

private void addProperty(String section,String line)
{
int equalIndex = line.indexOf(«=»);

if( equalIndex > 0 )
{
String name = section+’.'+line.substring(0,equalIndex).trim();
String value = line.substring(equalIndex+1).trim();
iniProperty.put(name,value);
}
}

public String getProperty(String section,String var,String def)
{
return iniProperty.getProperty(section+’.'+var,def);
}

public int getProperty(String section,String var,int def)
{
String sval = getProperty(section,var,Integer.toString(def));

return Integer.decode(sval).intValue();
}

public boolean getProperty(String section,String var,boolean def)
{
String sval = getProperty(section,var,def ? «True»:»False»);

return sval.equalsIgnoreCase(«Yes») ||
sval.equalsIgnoreCase(«True»);
}
}

Файлы Properties.

Этот формат распространён в Unix-мире. Он ещё проще ini-файлов, т.к. в нём отсутствует понятие секций — всё состоит из ключей и значений. Пример типичного файла:

# Database configuration
Database.Driver=sun.jdbc.odbc.JdbcOdbcDriver
Database.DataURL=jdbc:odbc:MyDatabase
Database.Prop.user=user
Database.Prop.password=password

В Java есть готовый класс для чтения/записи таких файлов (java.util.Properties), но с ним есть некоторые проблемы. Во первых для чтения невозможно задать кодировку файла, а это означает проблемы с русскими буквами. Во вторых стандартная функция записи сохраняет данные в порядке следования хэш-значений ключей, что значит — как ей больше понравится. Но это тоже легко разрешимо — достаточно написать свою читалку/писалку.
XML-файлы.

Этот формат подходит для многих целей, в том числе и для хранения настроек. XML-формат ориентирован на древовидные структуры, что довольно естественым образом отображается на объекты. Пример типичного файла:

<?xml version=»1.0″ encoding=»Windows-1251″?>

<!— Database configuration —>
<database
driver=»sun.jdbc.odbc.JdbcOdbcDriver»
dataURL=»jdbc:odbc:MyDatabase»>
<prop name=»user»>user</prop>
<prop name=»password»>password</prop>
</database>

Для чтения и записи таких файлов предназначены специальные библиотеки — так называемые XML-парсеры. Таких парсеров уже сделано довольно много, так что писать его самому нет большого смысла — достаточно лишь подобрать подходящий. Для парсеров было разработано два стандартных программных интерфейса — событийный (SAX) и иерархический (DOM). Есть также и парсеры со своим интерфейсом. Размер jar-а с парсером может варьироваться от нескольких килобайт до мегабайта — в зависимости от поддерживаемых интерфейсов и возможностей.

Для XML также написано несколько библиотек для универсального сохранения (сериализации) объектов в файлах XML. Такие библиотеки позволяют отделить алгоритм сохранения от самого объекта, а это, как уже упоминалось, имеет много плюсов.
Сериализация.

Под термином «сериализация» понимают запись содержимого объекта в поток двоичных данных. Обычно имеется в виду универсальный алгоритм, реализуемый классами java.io.ObjectOutputStream и java.io.ObjectInputStream. Пользоваться ими просто настолько, насколько это вообще возможно — обычно достаточно лишь отметить в классе поддержку при помощи интерфейса Serializable и отметить ключевым словом transient те поля объекта, которые сохранять не нужно. Собсно и всё. :-) Пример:

public class SerialObject implements java.io.Serializable
{
private String name;
private transient int state;

public SerialObject() {}
public SerialObject(String n) { name = n; }

public String getName() { return name; }
public void setState(int s) { state = s; }
}

Запись объектов:

SerialObject o = …;
OutputStream os = …;
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(o);

Чтение объектов:

InputStream is = …;
ObjectInputStream ois = new ObjectInputStream(is);
SerialObject o = (SerialObject)ois.readObject();

Использование сериализации — это один из самых простых вариантов по реализации, но и у него есть свои недостатки. Получаемые файлы являются двоичными, а значит в текстовом редакторе их уже не подправить — придётся делать редактирование параметров из программы. Кроме того, необходимо следить за изменением сохраняемых объектов, дабы не нарушить совместимость при изменении и развитии программы.
Базы данных.

В базах данных можно хранить любые данные, конфигурация программы — не исключение. Это имеет смысл в нескольких случаях:

Настройки связаны весьма сложным образом и древовидные структуры типа XML подходят плохо.
Доступ к настройкам должен быть только у авторизованых пользователей.
Доступ к этим данным должен быть и из других программ, например из генератора отчётов типа Crystal Reports.

БД могут применятся объектные или реляционные. Другие типы сейчас широкого распространения не имеют. Использовать хорошую объектную БД часто так же просто, как и сериализацию. Для реляционых баз можно применить объектную надстройку, которая также позволяет сильно упростить жизнь. Ну а можно делать обычные SELECT-ы.
Скрипты.

Использование скриптов — это один из самых экстремальных способов конфигурирования. Они позволяют добится максимальной гибкости программы за счёт вынесения логики наружу. В использовании скриптов надо тоже знать меру — в конце концов заказчик платит Вам за программу, решающую задачи, а не за ещё один интерпретатор или компилятор за который ему потребуется посадить ещё одного программиста. А то получается, как в том анекдоте — какую программу не начнёшь писать, всё компилятор получается.

Но часто без скриптов действительно тяжело. Типичные примеры — алгоритмы импорта/экспорта, алгоритмы проверок данных. Вы можете подготовить стандартный набор, а дальше настраивать скриптами под конкретные требования заказчика.

Для программ на Java в качестве скрипт-языка хорошо использовать язык Python в его Java-инкарнации под названием JPython. Там легко организовать двусторонюю связь между программой и скриптом. Если не будет хватать скорости интерпретации, то код на Python-е можно скомпилировать в байт-код — получится обычный Java-класс. Про JPython можно почитать на сайте http://www.jpython.org/ или в книжке Брюса Эккеля Thinking In Patterns with Java (доступна на http://www.bruceeckel.com/).
Пример программы с конфигурацией в XML.

В качестве примера можете посмотреть простенькую программы, использующей XML-файл в качестве конфигурационного. Сохраняемые параметры можно редактировать как из программы, так при помощи текстового редактора.

XMLConfig.java

Пример содержимого конфигурационного файла:

<?xml version=»1.0″ encoding=»Cp1251″?>

<program color=»ff0000ff» bounds=»263 231 392 195″>
Просто строчка
Вторая строчка</program>

В качестве XML-парсера используется Sun-овский парсер в режиме DOM. На таком простом примере не видно особых преимуществ формата XML над теми же файлами properties. Они становятся заметны только в достаточно сложных программах, где становится необходимо хранить списки однотипных параметров или же содержимое объектов с уровнем вложенности два или более.

Источник: http://www.javaportal.ru/java/articles/javaconfigprograms.html
Автор: Сергей Астахов

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

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

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