Простой пример работы с Property файлами в Java
Property файлы присутствуют практически в каждом проекте, и сейчас я вам покажу простой пример их использования, а также расскажу, зачем они и где используются.
Шаг 0. Создание проекта
Начнем с того что создадим простой Maven проект, указав название и имя пакета:
Структура, которая получится в конце проекта довольно таки простая.

Как видите у нас только два файла, первый – Main.java, а второй – config.properties.
Шаг 2. Добавляем конфигурационные данные в проперти файл
Проперти файлы либо файлы свойств – предназначены, для того чтобы хранить в них какие-то статические данные необходимые проект, например логин и пароль к БД.
Давайте добавим в наш config.properties логин и пароль (это любые данные, для того чтобы продемонстрировать работу с property файлами).
Содержимое config.properties:
<ключ> – это уникальное имя, по которому можно получить доступ к значению, хранимому под этим ключом.
<значение> – это текст, либо число, которое вам необходимо для выполнения определённой логики в вашей программе.
Шаг 3. Получаем Property данные
Как можно видеть в структуре проекта выше, там есть класс Main.java давайте его создадим и напишем в нем следующее:
Обращаясь к property.getProperty(<ключ>) – вы получаете его значение.
Чтение конфигурационных файлов в Java: nProperty
Многие разработчики сталкиваются с необходимостью чтения конфигурационных (*.ini, *.prop, *.conf, etc.) файлов в разрабатываемых приложениях. В Java есть стандартный класс Properties, с помощью которого можно очень легко загрузить ini-файл и прочитать его свойства. При большом объеме конфигурационных файлов чтение и запись настроек в объекты превращается в очень нудную и рутинную работу: создать объект Properties, конвертировать каждую настройку в нужный формат и записать его в поле.
Библиотека nProperty (Annotated Property) призвана упростить этот процесс, сократив примерно в два раза требуемый код для написания загрузчиков настроек.
Чтобы показать, каким образом возможно обещанное сокращение кода в два раза, ниже приведены два примера: в первом примере используется стандартный класс Properties, во-втором — nProperty.
Статья и сама библиотека nProperty написана моим другом и товарищем по цеху Yorie для внутрикомандных повседневных нужд, и так как он, к сожалению, не имеет в данный момент инвайта на хабре, я взял на себя смелость, с его согласия, опубликовать сие творение для «хабровских» масс.
Содержание
Просто о главном
В обоих примерах будет использован один и тот же файл конфигурации:
Пример №1. Загрузка конфигурации с помощью стандартного класса Properties.
Пример №2. Загрузка конфигурации с помощью nProperty.
Пожалуй, из этих красивых примеров вытекает факт, что код может быть сокращен даже более, чем в два раза 🙂 В этих примерах не освещены темы присутствия в полях классов переменных, не относящихся к файлам конфигурации, а также еще несколько тонких нюансов. Но обо всем по порядку.
Чтение примитивных и стандартных типов
Во втором вышеприведенном примере стоит обратить внимание на аннотацию @Сfg. Она и является причиной сократившегося кода. Библиотека nProperty основана на аннотациях, которые могут быть применены к классам, полям и методам классов.
Чтобы прочитать из конфигурационного файла настройки, тип которых относится к примитивным, достаточно каждое поле класса обозначить аннотацией @Сfg:
- Integer/int
- Short/short
- Double/double
- Long/long
- Boolean/boolean
- String
- Character/char
- Byte/byte
- AtomicInteger, AtomicLong, AtomicBoolean
- BigInteger, BigDecimal
Десериализация в массивы и коллекции
Помимо стандартных типов также возможна десериализация в массивы с одним условием — тип массива должен принадлежать множеству стандартных типов:
В случае с массивами библиотека сама позаботится о том, чтобы проинициализировать массив нужного размера.
Обратите внимание на аннотации у SOME_INT_ARRAY и SOME_SHORT_ARRAY. По умолчанию nProperty использует в качестве разделителя символ «;». Его можно легко переопределить, указав в аннотации к полю свойство splitter. И, как можно заметить, разделителем может выступать полноценное регулярное выражение.
Помимо массивов возможно использование коллекций, а именно — списков. Здесь необходимым является одно условие — коллекция должна быть обязательно проинициализирована до запуска чтения конфигурации. Это связано с тем, что экземпляры объектов коллекций могут быть разными (ArrayList, LinkedList и т.д.):
В остальном для коллекций сохраняются все свойства десериализации массивов.
Десериализация в пользовательские типы
В качестве дополнительной функции библиотека может работать с пользовательскими классами. Пользовательский тип обязательно должен иметь конструктор: MyClass(String), в противном случае будет вызвано исключение. Уровень видимости конструктора не имеет значения, он может быть как public, так и private:
Как видите, библиотеке все равно, что нужный конструктор обозначен модификатором private. В результате в поле value класса T будет записано значение из файла конфигурации.
Модификаторы уровней доступа
Стоит отметить, что библиотеке nProperty абсолютно все равно, какие модификаторы доступа имеет поле, метод или конструктор — библиотека работает через механизм Reflections и управляет этими модификаторами самостоятельно. Конечно же, вмешательство в модификаторы никак не коснется других частей приложения, к которым библиотека отношения не имеет.
Инициализация всех членов класса
В предыдущих примерах видно, что при большом количестве полей в конфигурации придется написать большое кол-во аннотаций @Сfg. Чтобы избежать этой рутинной работы nProperty позволяет добавить аннотацию к самому классу, тем самым обозначив все поля класса как потенциальные поля для записи в них настроек из файла конфигурации:
Здесь стоит обратить внимание на член класса log. Ему назначена аннотация @Сfg с включенным свойством ignore. Это свойство означает, что данное поле не будет использоваться библиотекой при чтении конфигурации, а попросту будет пропущено. Данное свойство следует использовать только в случае, когда аннотация действует на весь класс, как показано в примере выше.
Значения по умолчанию
Одно из замечательных свойств библиотеки в том, что если свойство отсутствует в файле конфигурации, то поле класса никогда не будет изменено. Это позволяет легко выставлять значения по умолчанию прямо в декларации поля класса:
В данном случае после парсинга конфигурации в поле WRONG_PROPERTY будет храниться все то же значение 9000.
Переопределение имен
В случаях, когда имя поля класса не совпадает с именем конфигурации в конфигурационном файле, его можно принудительно переопределить:
Естественно, если есть возможность сохранять равнозначность имен в коде и в файлах конфигурации, то лучше так и делать — это избавит от необходимости аннотировать каждое поле класса.
Работа с не статичными полями классов
Библиотека способна работать как с классами, так и с их экземплярами. Это определяется путем различных вызовов метода ConfigParser.parse():
Как видно, в примере использованы два разных вызова одного и того же метода. После отработки метода ConfigParser.parse(Example11.class, «config/example.ini») в SOME_INT_VALUE будет нуль, причем это совершенно не зависит от файла конфигурации, потому что данное поле не является статичным и не может быть использовано без экземпляра объекта.
Сразу после второго вызова ConfigParser.parse(new Example11(), «config/example.ini») поле SOME_INT_VALUE для созданного объекта примет значение в соответствии с содержанием файла конфигурации.
Следует аккуратно пользоваться этой возможностью библиотеки, так как могут появиться ситуации, когда конфигурация не будет прогружаться по «непонятной» причине, а на самом деле окажется, что просто не был проставлен модификатор static.
Использование методов
Давайте представим, что во время чтения некоторого свойства из файла конфигурации необходимо выполнить проверку его содержимого, или, к примеру, десериализовать содержимое особым образом.
- самостоятельно проверить или изменить значение после того, как библиотека проанализирует файл настроек и заполнит все поля класса
- создать в качестве типа свой класс-обертку с конструктором (как было показано выше)
- исключить поле класса из списка свойств и назначить его методу
Самый удобный и корректный способ — №3. Библиотека nProperty позволяет работать не только с полями, но и с методами:
Здесь в метод checkIntArray(String) в качестве первого параметра будет передано значение SOME_INT_ARRAY из файла конфигурации. Это очень удобный механизм для случаев, когда стандартные решения библиотеки не подходят. В методе-обработчике можно делать все, что угодно.
Однако, стоит отметить, что в случае работы с методами библиотека не использует механизм разделителей, то есть, на данный момент невозможно организовать автоматическое разбиение свойства в массив.
Как и прежде поддерживается преобразование типов, если тип первого параметра метода отличен от String.
Как и с полями класса, если имя метода эквивалентно имени настройки в файле конфигурации, то можно опустить задание имени в аннотации.
Обработка событий
Библиотека nProperty позволяет обрабатывать некоторые события во время чтения конфигурации. Для того, чтобы реализовать обработку событий, необходимо реализовать интерфейс IPropertyListener и все его методы. Вызов событий возможен только в случае работы с полноценными объектами, экземплярами класса, реализующего интерфейс IPropertyListener.
- onStart(String path) — отправляется перед началом загрузки файла конфигурации
- onPropertyMiss(String name) — вызывается в случае, если некоторая именованная конфигурация не была найдена в файле настроек, но была обозначена в классе аннотацией @Сfg
- onDone(String path) — вызывается при завершении загрузки файла конфигурации
- onInvalidPropertyCast(String name, String value) — вызывается в случае, когда удалось прочитать значение настройки из файла конфигурации, но не удалось привести это значение к типу соответствующего поля класса
В приведенном примере будут вызваны все 4 события. Событие onPropertyMiss будет вызвано из-за поля SOME_MISSED_VALUE, которое отсутствует в файле конфигурации. Событие onInvalidPropertyCast будет вызвано из-за неверного типа поля SOME_INT_ARRAY.
Использование потоков и дескрипторов файлов
Библиотека умеет принимать на вход не только имена файлов, также возможна передача объекта java.io.File, или потока данных, производного от абстрактного класса java.io.InputStream:
Как видно, в приведенном примере в случае работы с потоком, библиотека требует дополнительно указать название конфигурации, так как невозможно его получить из низкоуровневого объекта FileInputStream. Название не является важной частью и будет использовано библиотекой для отображения информации (в том числе, при работе с событиями).
Таким образом, данные могут быть получены не только из файловой системы, но и от любого источника данных, работающего по стандартам Java. Умение работать с java.io.InputStream дает возможность библиотеке быть успешно примененной в операционных системах Android:
Замечания
В связи с не очень прозрачной работой SecurityManager’a библиотека имеет ограничение на тип задаваемого поля конфигуратора: поле не должно иметь модификатора final.
Properties
В Java принято делать программы гибкими и легко настраиваемыми. Иногда еще говорят легко конфигурируемыми (от конфигурация).
Например, твоя программа раз в час копирует файлы из некоторой директории, архивирует их и отсылает тебе на email. Для этого программе надо знать директорию, откуда брать файлы и email, куда их посылать. Такие данные принято хранить не в коде программы, а в отдельных файлах свойств – properties-файлах.
Данные в таком файле хранятся в виде пар ключ-значение, разделенные знаком равно.
Слева от знака равно – имя (ключ), справа – значение.
— Т.е. это что-то типа текстового представления HashMap?
Для удобной работы с такими файлами в Java есть специальный класс – Properties. Класс Properties унаследован от Hashtable<Object,Object>. Его даже можно рассматривать как HashTable, который умеет загружать себя из файла.
| Метод | Описание |
|---|---|
| void load(Reader reader) | Загружает свойства из файла, представленного объектом Reader |
| void load(InputStream inStream) | Загружает свойства из файла, представленного объектом InputStream |
| void loadFromXML(InputStream in) | Загружает свойства из XML-файла |
| Object get(Object key) | Возвращает значение по ключу. Метод унаследован от HashTable |
| String getProperty(String key) | Возвращает значение свойства (строку) по ключу |
| String getProperty(String key, String defaultValue) | Возвращает значение свойства по ключу или defaultValue, если такого ключа нет |
| Set<String> stringPropertyNames() | Возвращает список всех ключей |
Т.е. фактически тебе нужно выполнить всего две операции – загрузить в объект Properties данные из какого-нибудь файла, а затем получить эти свойства с помощью метода getProperty(). Ну и не забывай, что можешь пользоваться объектом Properties как HashMap.
Вот тебе пример:
— Ага. Т.е. мы создаем объект Properties, затем передаем в него файл. В метод load, а затем просто вызываем getProperty. Так?
— А ты еще говорил, что им можно пользоваться как HashMap? Что ты имел в виду?
— Класс Properties унаследован от Hashtable, а это – тот же HashMap, просто все методы его синхронизированы. Вот так можно просто вывести на экран все значения из файла свойств:
— Ага. Вроде все стало на свои места. Спасибо, Риша, буду пользоваться такой крутой штукой.
Пример работы с Properties в Java
В наше время почти в каждом проекте используются различные статические файлы и данные, необходимые классам для безопасного и быстрого доступа. Примером таких конфигурационных файлов являются так называемые Файлы свойств, более известные как *.properties.
.properties — файловое расширение для файлов, которые используются в основном в технологиях Java для хранения конфигурационных параметров программы. Они также могут использоваться для хранения переменных строчной типа для локализации; они известны как Property Resource Bundles.
Каждый параметр сохраняется парой двух переменных строчной типа, одна сохраняет имя параметра (так называемый ключ), а другой сохраняет значение. Каждая строка файла .properties, как правило, хранит значения одного параметра. Несколько форматов являются допустимыми для .properties, в том числе ключ = значение, ключ: значение и сам ключ.
.properties файлы могут использовать знак решетки (#) или восклицательный знак (!) как первый, не пустой символ в строке для обозначения последующего текста в качестве комментария.
Например работы с .properties в Java
Для начала подготовим среду для работы и создадим проект (Рисунок 1):

Рисунок 1 — Пример проекта на Maven — очень удобно и функционально
Теперь посмотрим на структуру созданного проекта (Рисунок 2):

Рисунок 2 — Структура нашего проекта для работы с properties
Как видим, в папке Resources уже есть файл под названием config.properties. Вам нужно будет его создать вручную и наполнить данными, а именно (Рисунки 3-4):

Рисунок 3 — Создание файла путем нажатия ПКМ на папке resources -> New -> File

Рисунок 4 — Вводим название файла с расширением properties в появившемся диалоге и нажимаем ОК
Далее прописываем необходимые ключи и значения, как на рисунке 5:

Рисунок 5 — Файл с необходимыми параметрами
Пример обращения к файлу properties
Ниже представлена программа, демонстрирующая обращение к нашему конфигурационному файлу: