Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Чтение конфигурационных файлов в Java: nProperty

Anna | 4.06.2014 | нет комментариев
Многие разработчики сталкиваются с необходимостью чтения конфигурационных (*.ini, *.prop, *.conf, etc.) файлов в разрабатываемых приложениях. В Java есть типовой класс Properties, с поддержкой которого дозволено дюже легко загрузить ini-файл и прочитать его свойства. При большом объеме конфигурационных файлов чтение и запись настроек в объекты превращается в дюже нудную и рутинную работу: сделать объект Properties, конвертировать всякую настройку в необходимый формат и записать его в поле.

Библиотека nProperty (Annotated Property) призвана упростить данный процесс, сократив приблизительно в два раза требуемый код для написания загрузчиков настроек.

Дабы показать, каким образом допустимо обещанное сокращение кода в два раза, ниже приведены два примера: в первом примере применяется типовой класс Properties, во-втором — nProperty.


Статья и сама библиотека nProperty написана моим ином и товарищем по цеху Yorie для внутрикомандных повседневных нужд, и так как он, к сожалению, не имеет в данный момент инвайта на прогре, я взял на себя храбрость, с его согласия, опубликовать сие творение для «прогровских» масс.

Оглавление

  1. Легко о основном
  2. Чтение простых и стандартных типов
  3. Десериализация в массивы и коллекции
  4. Десериализация в пользовательские типы
  5. Модификаторы ярусов доступа
  6. Инициализация всех членов класса
  7. Значения по умолчанию
  8. Переопределение имен
  9. Работа с не неподвижными полями классов
  10. Применение способов
  11. Обработка событий
  12. Применение потоков и дескрипторов файлов
  13. Недочеты
  14. Лицензия
  15. Ссылки

Легко о основном


В обоих примерах будет использован один и тот же файл конфигурации:

SOME_INT_VALUE = 2
SOME_DOUBLE_VALUE = 1.2
SOME_STRING_VALUE = foo
SOME_INT_ARRAY = 1;2;3

Пример №1. Загрузка конфигурации с поддержкой стандартного класса Properties.

public class Example1
{
	private static int SOME_INT_VALUE = 1;
	private static String SOME_STRING_VALUE;
	private static int[] SOME_INT_ARRAY;
	private static double SOME_DOUBLE_VALUE;

	public Example1() throws IOException
	{
		Properties props = new Properties();
		props.load(new FileInputStream(new File("config/example.ini")));

		SOME_INT_VALUE = Integer.valueOf(props.getProperty("SOME_INT_VALUE", "1"));
		SOME_STRING_VALUE = props.getProperty("SOME_STRING_VALUE");
		SOME_DOUBLE_VALUE = Double.valueOf(props.getProperty("SOME_DOUBLE_VALUE", "1.0"));

		// Представим, что в настройках находится список целых через точку с запятой
		String[] parts = props.getProperty("SOME_INT_ARRAY").split(";");
		SOME_INT_ARRAY = new int[parts.length];
		for (int i = 0; i < parts.length;   i)
		{
			SOME_INT_ARRAY[i] = Integer.valueOf(parts[i]);
		}
	}

	public static void main(String[] args) throws IOException
	{
		new Example1();
	}
}

Пример №2. Загрузка конфигурации с поддержкой nProperty.

@Cfg
public class Example2
{
	private static int SOME_INT_VALUE = 1;
	private static String SOME_STRING_VALUE;
	private static int[] SOME_INT_ARRAY;
	private static double SOME_DOUBLE_VALUE;

	public Example2() throws NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, InvocationTargetException
	{
		ConfigParser.parse(Example2.class, "config/example.ini");
	}

	public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IOException, IllegalAccessException
	{
		new Example2();
	}
}


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

Чтение простых и стандартных типов


Во втором вышеприведенном примере стоит обратить внимание на аннотацию @Сfg. Она и является поводом сократившегося кода. Библиотека nProperty основана на аннотациях, которые могут быть применены к классам, полям и способам классов.

Дабы прочитать из конфигурационного файла настройки, тип которых относится к примитивным, довольно всякое поле класса обозначить аннотацией @Сfg:

public class Example3
{
    @Cfg
    private static int SOME_INT_VALUE;

    @Cfg
    private static short SOME_SHORT_VALUE;

    @Cfg
    private static long SOME_LONG_VALUE;

    @Cfg
    private static Double SOME_DOUBLE_VALUE;

    /* ... */
}


Библиотека nProperty поддерживает довольно богатенький комплект стандартных типов:

  • Integer/int
  • Short/short
  • Double/double
  • Long/long
  • Boolean/boolean
  • String
  • Character/char
  • Byte/byte
  • AtomicInteger, AtomicLong, AtomicBoolean
  • BigInteger, BigDecimal


Все эти перечисленные типы могут быть использованы в примере выше.

Десериализация в массивы и коллекции


Помимо стандартных типов также допустима десериализация в массивы с одним условием — тип массива должен принадлежать множеству стандартных типов:

/*
    В файле конфигурации находится следующее:
        SOME_INT_ARRAY = 1--2--3
        SOME_SHORT_ARRAY = 3>2<1
        SOME_BIGINTEGER_ARRAY = 1;2;3
 */
public class Example5
{
    @Cfg(splitter = "--")
    private static int[] SOME_INT_ARRAY;
    @Cfg(splitter = "[><]")
    private static short[] SOME_SHORT_ARRAY;
    @Cfg
    private static BigInteger[] SOME_BIGINTEGER_ARRAY;
}


В случае с массивами библиотека сама позаботится о том, Дабы проинициализировать массив надобного размера.

Обратите внимание на аннотации у SOME_INT_ARRAY и SOME_SHORT_ARRAY. По умолчанию nProperty использует в качестве разделителя символ “;”. Его дозволено легко переопределить, указав в аннотации к полю качество splitter. И, как дозволено подметить, разделителем может выступать полновесное регулярное выражение.

Помимо массивов допустимо применение коллекций, а именно — списков. Тут нужным является одно условие — коллекция должна быть непременно проинициализирована до запуска чтения конфигурации. Это связано с тем, что экземпляры объектов коллекций могут быть различными (ArrayList, LinkedList и т.д.):

public class Example6
{
    @Cfg
    private static List<Integer> SOME_ARRAYLIST_COLLECTION = new ArrayList<>();

    @Cfg
    private static List<Integer> SOME_LINKEDLIST_COLLECTION = new LinkedList<>();
}


В остальном для коллекций сохраняются все свойства десериализации массивов.

Десериализация в пользовательские типы


В качестве дополнительной функции библиотека может трудиться с пользовательскими классами. Пользовательский тип непременно должен иметь конструктор: MyClass(String), в отвратном случае будет вызвано исключение. ?рус видимости конструктора не имеет значения, он может быть как public, так и private:

public class Example8
{
    private static class T
    {
        private final String value;

        private T(String value)
        {
            this.value = value;
        }

        public String getValue() { return value; }
    }

    @Cfg
    private static T CUSTOM_CLASS_VALUE;
}


Как видите, библиотеке все равно, что необходимый конструктор обозначен модификатором private. В итоге в поле value класса T будет записано значение из файла конфигурации.

Модификаторы ярусов доступа


Стоит подметить, что библиотеке nProperty безусловно все равно, какие модификаторы доступа имеет поле, способ либо конструктор — библиотека работает через механизм Reflections и управляет этими модификаторами самосильно. Безусловно же, ввязывание в модификаторы никак не коснется других частей приложения, к которым библиотека отношения не имеет.

Инициализация всех членов класса


В предыдущих примерах видно, что при большом числе полей в конфигурации придется написать огромное кол-во аннотаций @Сfg. Дабы избежать этой рутинной работы nProperty разрешает добавить аннотацию к самому классу, тем самым обозначив все поля класса как потенциальные поля для записи в них настроек из файла конфигурации:

@Cfg
public class Example7
{
    /* Все поля класса будут использованы как поля для чтения настроек */
    private static int SOME_INT_VALUE = 1;
    private static String SOME_STRING_VALUE;
    private static int[] SOME_INT_ARRAY;
    private static double SOME_DOUBLE_VALUE;
    private static List<Integer> SOME_ARRAYLIST_COLLECTION = new ArrayList<>();
    private static List<Integer> SOME_LINKEDLIST_COLLECTION = new LinkedList<>();

    @Cfg(ignore = true)
    private final static Logger log = Logger.getAnonymousLogger();
}


Тут стоит обратить внимание на член класса log. Ему назначена аннотация @Сfg с включенным свойством ignore. Это качество обозначает, что данное поле не будет применяться библиотекой при чтении конфигурации, а просто будет пропущено. Данное качество следует применять только в случае, когда аннотация действует на каждый класс, как показано в примере выше.

Значения по умолчанию


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

/* Файл конфигурации не содержит свойства WRONG_PROPERTY */
@Cfg
public class Example9
{
    private int WRONG_PROPERTY = 9000;

    private int SOME_INT_VALUE;
}


В данном случае позже парсинга конфигурации в поле WRONG_PROPERTY будет храниться все то же значение 9000.

Переопределение имен


В случаях, когда имя поля класса не совпадает с именем конфигурации в конфигурационном файле, его дозволено принудительно переопределить:

public class Example10
{
    @Cfg("SOME_INT_VALUE")
    private int myIntValue;
}


Безусловно, если есть вероятность сберегать равнозначность имен в коде и в файлах конфигурации, то отменнее так и делать — это избавит от необходимости аннотировать всякое поле класса.

Работа с не неподвижными полями классов


Библиотека способна трудиться как с классами, так и с их экземплярами. Это определяется путем разных вызовов способа ConfigParser.parse():

@Cfg
public class Example11
{
    private static int SOME_SHORT_VALUE;

    private int SOME_INT_VALUE;

    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, InvocationTargetException
    {
        ConfigParser.parse(Example11.class, "config/example.ini"); // В данном вызове не будет применяться переменная SOME_INT_VALUE

        ConfigParser.parse(new Example11(), "config/example.ini");
    }
}


Как видно, в примере использованы два различных вызова одного и того же способа. Позже отработки способа ConfigParser.parse(Example11.class, «config/example.ini») в SOME_INT_VALUE будет нуль, причем это абсолютно не зависит от файла конфигурации, потому что данное поле не является неподвижным и не может быть использовано без экземпляра объекта.

Сразу позже второго вызова ConfigParser.parse(new Example11(), «config/example.ini») поле SOME_INT_VALUE для сделанного объекта примет значение в соответствии с оглавлением файла конфигурации.

Следует старательно пользоваться этой вероятностью библиотеки, так как могут возникнуть обстановки, когда конфигурация не будет прогружаться по «малопонятной» причине, а на самом деле окажется, что легко не был проставлен модификатор static.

Применение способов


Давайте предположим, что во время чтения некоторого свойства из файла конфигурации нужно исполнить проверку его содержимого, либо, к примеру, десериализовать содержимое специальным образом.

Существует три решения в таких обстановках:

  1. самосильно проверить либо изменить значение позже того, как библиотека проанализирует файл настроек и заполнит все поля класса
  2. сделать в качестве типа свой класс-обертку с конструктором (как было показано выше)
  3. исключить поле класса из списка свойств и назначить его способу

Самый комфортный и правильный метод — №3. Библиотека nProperty разрешает трудиться не только с полями, но и с способами:

public class Example12
{
    private static List<Integer> VALUE_CHECK = new ArrayList<>();

    @Cfg("SOME_INT_ARRAY")
    private void checkIntArray(String value)
    {
        String[] values = value.split("--");

        for (String val : values)
        {
            try
            {
                /* ограничим значение интервалом [0,100] */
                VALUE_CHECK.add(Math.max(0, Math.min(100, Integer.parseInt(val))));
            }
            catch (Exception ignored) {}
        }
    }
}


Тут в способ checkIntArray(String) в качестве первого параметра будет передано значение SOME_INT_ARRAY из файла конфигурации. Это дюже комфортный механизм для случаев, когда типовые решения библиотеки не подходят. В способе-обработчике дозволено делать все, что желательно.

Впрочем, стоит подметить, что в случае работы с способами библиотека не использует механизм разделителей, то есть, на данный момент немыслимо организовать механическое разбиение свойства в массив.

Как и раньше поддерживается реформирование типов, если тип первого параметра способа хорош от String.

Как и с полями класса, если имя способа равнозначно имени настройки в файле конфигурации, то дозволено опустить задание имени в аннотации.

Обработка событий


Библиотека nProperty разрешает обрабатывать некоторые события во время чтения конфигурации. Для того, Дабы реализовать обработку событий, нужно реализовать интерфейс IPropertyListener и все его способы. Вызов событий допустим только в случае работы с полновесными объектами, экземплярами класса, реализующего интерфейс IPropertyListener.

Поддерживаемые события:

  • onStart(String path) — отправляется перед началом загрузки файла конфигурации
  • onPropertyMiss(String name) — вызывается в случае, если некоторая именованная конфигурация не была обнаружена в файле настроек, но была обозначена в классе аннотацией @Сfg
  • onDone(String path) — вызывается при заключении загрузки файла конфигурации
  • onInvalidPropertyCast(String name, String value) — вызывается в случае, когда удалось прочитать значение настройки из файла конфигурации, но не удалось привести это значение к типу соответствующего поля класса

@Cfg
public class Example13 implements IPropertyListener
{
    public int SOME_INT_VALUE;
    public int SOME_MISSED_VALUE;
    public int SOME_INT_ARRAY;

    @Override
    public void onStart(String path)
    {

    }

    @Override
    public void onPropertyMiss(String name)
    {

    }

    @Override
    public void onDone(String path)
    {

    }

    @Override
    public void onInvalidPropertyCast(String name, String value)
    {

    }

    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, InvocationTargetException
    {
        ConfigParser.parse(new Example13(), "config/example.ini");
    }
}


В приведенном примере будут вызваны все 4 события. Событие onPropertyMiss будет вызвано из-за поля SOME_MISSED_VALUE, которое отсутствует в файле конфигурации. Событие onInvalidPropertyCast будет вызвано из-за неверного типа поля SOME_INT_ARRAY.

Применение потоков и дескрипторов файлов


Библиотека может принимать на вход не только имена файлов, также допустима передача объекта java.io.File, либо потока данных, производного от абстрактного класса java.io.InputStream:

@Cfg
public class Example14
{
    public int SOME_INT_VALUE;
    public int SOME_MISSED_VALUE;
    public int SOME_INT_ARRAY;

    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, InvocationTargetException
    {
        ConfigParser.parse(new Example14(), "config/example.ini");
        ConfigParser.parse(new Example14(), new File("config", "example.ini"));
        ConfigParser.parse(new Example14(), new FileInputStream("config/example.ini"), "config/example.ini");
    }
}


Как видно, в приведенном примере в случае работы с потоком, библиотека требует добавочно указать наименование конфигурации, так как немыслимо его получить из низкоуровневого объекта FileInputStream. Наименование не является главной частью и будет использовано библиотекой для отображения информации (в том числе, при работе с событиями).

Таким образом, данные могут быть получены не только из файловой системы, но и от всякого источника данных, работающего по эталонам Java. Знание трудиться с java.io.InputStream дает вероятность библиотеке быть удачно использованной в операционных системах Android.

Недочеты


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

Лицензия


Разработка библиотеки не преследовала торговых целей и преследовать не будет.

Ссылки

Будем применять? :)

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Проголосовало 9 человек. Воздержалось 4 человека.

 

 Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB