Размещение файлов для инициализации PropertiesResourceBundle - наследника ResourceBandle
Давно не писал ничего про программирование. Не то, чтобы не было повода, просто руки не доходили. А тут, внезапно, столкнулся с проблемой, которую, казалось, разобрал и решил очень давно.
Наверное, ни для кого не секрет (речь, конечно, про тех, кто кодирует), что на работу программиста большое влияние оказывает набор инструментов, помогающих (или же, мешающих 🙃) быстро писать хороший код. Ну ладно, слово "быстро" можно опустить, как, собственно, и определение "хороший". После этих модификаций останется утверждение, на мой взгляд, неоспоримое: используемые инструменты оказывают существенное влияние на работу программиста.
Ясно, что писать код можно и в notepad
-е, вопрос в том, как это будет получаться. Как мне кажется, однако, различные навороченные IDE, облегчая, в принципе, решение рутинных каждодневных задач, тем не менее, в определенной степени, расхолаживают своих пользователей, то есть, нас, программистов. Не хочу быть понятым неправильно, фраза про "расхолаживание" не означает, что я ратую за отказ от хороших сред программирования. Я лишь за то, чтобы предоставляемая автоматизация не заменяла собой знания, использование которых призвана облегчить.
Если, вдруг, поддаться лени и просто пользоваться плодами автоматизации, без глубокого понимая того, а что, собственно, происходит "под капотом", то можно довольно чувствительно "встрять" при переходе на другие средства и инструменты. Они, наверняка, позволят делать все то же самое, что и старые, а, может, и больше (не зря же вы меняете средство производства), но вот с некоторыми особенностями. А дьявол, как известно, и кроется в деталях. Если же иметь представление о предмете, вокруг которого и строятся все эти помощники, то переход между ними будет происходить чуть менее болезненно. Да и вообще, знаний лишних не бывает.
Но что-то я опять ударился в философию. Вернусь, пожалуй, к теме. Класс java.util.ResourceBundle
используют, обычно, для хранения текстовых и других ресурсов, которые зависят от используемого языкового окружения. Я, в свою очередь, люблю пользоваться этим классом для хранения текстовых сообщений об ошибках. Во-первых, это позволяет иметь упорядоченное хранилище для подобных строк - все всегда под рукой, не надо искать разбросанные по исходникам строковые константы. Во-вторых, если, вдруг, взбрело в голову предоставить перевод на какой-нибудь язык, все делается очень и очень просто. Вроде бы, где тут может быть подвох? Как обычно, везде.
Начинается вся эта история с того, что, несмотря на использовании в тексте программы именно класса ResourceBundle
, на самом деле, создаются и задействуются экземпляры двух его наследников: PropertiesResourceBundle
и ListResourceBundle
. И это ничуть не удивительно: ResourceBundle
- абстрактный класс, а экземпляр такого класса создать, как известно, нельзя, зато можно замечательно использовать такой класс для доступа к методам объектов неабстрактных наследников.
Простейший код по получению нужного объекта выглядит, примерно, следующим образом:
...
import java.util.ResourceBundle;
...
ResourceBundle bndl;
...
bndl = ResourceBundle.getBundle("fully.qualified.bundle.name");
...
То есть, объект (экземпляр либо PropertiesResourceBundle
либо ListResourceBundle
) создается при помощи вызова getBundle
. В приведенном примере использована самая простая форма этого метода, у которого есть множество инкарнаций, но это сейчас к делу не относится. Более интересен вопрос, объект какого класса будет создан: PropertiesResourceBundle
или ListResourceBundle
.
Имеется два основных режима работы с потомками ResourceBundle
: один - создать наследника от ListResourceBundle
и заполнить его нужными данными, второй - создать специальный текстовый файл с расширением properties
, заполнив его строками вида:
key=value
key1=value1
...
То есть, если отбросить пафос, надо создать самый обычный текстовый файл, такой же, как и файл, используемый для сохранения экземпляров класса Properties
Java.
В зависимости от того, какой режим работы вы используете в каждом конкретном случае, значение параметра fully.qualified.bundle.name
интерпретируется по разному. В случае, если вы пошли по пути создания наследника от ListResourceBundle
, то параметр fully.qualified.bundle.name
должен содержать полное квалифицированное имя вашего класса, например, com.mycompany.myproject.MyResourceBundle
, где имя вашего класса - MyResourceBundle
, а размещен он в пакете com.mycompany.myproject
. Если же выбрали путь использования файла со строками key=value
, то параметр fully.qualified.bundle.name
должен содержать полное квалифицированное имя вашего файла, например, com.mycompany.myproject.MyResourceFile
, где имя вашего файла - MyResourceFile.properties
, а размещен он в пакете com.mycompany.myproject
.
Сам же метод getBundle
поступает очень просто: он ищет класс-наследник ListResourceBundle
с именем, указанным в параметре, и, если находит - использует его. Если же не находит, то пытается найти файл с именем, указанным в параметре (добавив расширение properties
), и, если находит, создает экземпляр класса PropertiesResourceBundle
, инициализируя его содержимым файла. Ну а если и файла не находит, тогда выстреливается исключение MissingResourceException
.
Надо сказать, что алгоритм поиска класса или файла по имени, который я привел, очень сильно упрощен. Но мне не хочется сейчас останавливаться на правилах, которые применяются во время поиска класса или файла в зависимости от языкового окружения (или явно указанного языкового параметра Locale
), отмечу лишь, что добавление расширения к имени файла - лишь часть этих правил. На самом деле, к имени файла или класса можно приписывать дополнительные части, определяющие, для какого языка и для какой страны в нем содержится информация. То есть, можно, например, создать пару файлов: com.mycompany.myproject.MyResourceFile_ru
для сообщений на русском языке, и com.mycompany.myproject.MyResourceFile_en
- для сообщений на английском. Метод getBundle
из примера выше, проанализирует текущие языковые настройки и использует нужный файл. Но это - лишь элементарный пример, все может быть значительно круче.
Из-за того, что значение параметра метода getBundle
лишь частично совпадает с реальным именем класса или файла, этот параметр называют базовым именем. При указании полного квалифицированного имени класса или файла в этом параметре, в качестве разделителя, обычно, используется точка (.
), но, для обратной совместимости, разрешается использовать и символ /
при указании имен файлов (то есть, когда будет использоваться PropertiesResourceBundle
).
Предположим, с базовым именем, указываемом в методе getBundle
, все более или менее ясно. Теперь вопрос: а где должен быть размещен соответствующий класс или файл? С классом-наследником ListResourceBundle
все довольно просто - так как это обычный java
файл, он должен быть расположен в каталоге с исходными кодами других классов, входящих в состав того же пакета. А вот с файлом в формате Java Properties
есть, что называется, нюансы.
Тут я сделаю небольшое отступление. Совсем небольшое. На тему используемой интегрированной среды разработки. Когда я начинал использовать Java, пришлось попробовать несколько IDE. В 2005 или 2006 году это был JBuilder X (почему-то, дальше не пошло, может, потому, что это был уже не Borland?), потом JDeveloper (потому, что в самом начале это был JBuilder), затем, когда все стали делать свои IDE на базе Eclispe, который вообще никак не хотел со мной находить общий язык, я стал счастливым пользователем NetBeans. Сейчас, когда NetBeans мучительно переживает очередную смену владельца, я, находясь, в полной растерянности, бросаюсь из крайности (Eclipse Che) в крайность (IntelliJ IDEA), продолжая пока использовать старенькую, но проверенную временем версию 8.2 того же NetBeans.
Так вот, в то время, когда только-только состоялось мое знакомство с классом ResourceBundle
, я был уже пользователем NetBeans. И главными проблемами для меня было разобраться, как включить properties
файл в нужный jar
, и как создать и проинициализировать этим файлом экземпляр PropertiesResourceBundle
. Скажу честно, последний мне нравился значительно больше, чем ListResourceBundle
, просто потому, что мне казалось (и кажется), что проще написать несколько строк в текстовом файле, нежели писать Java код, призванный решить теже задачи. Хотя, может я и не прав.
Надо сказать, что эта проблема (включение файла в результирующий jar
) довольно долго оставалась для меня неразрешимой. Чтобы я не делал, вызов getBundle
"выстреливал" MissingResourceException
. Тому было много причин. Всех перепитий я не помню, но точно могу сказать, что документацию надо читать тщательней. Причем, как к классу, так и к IDE. Каких только ошибок по именованию файла и его расположению я не далал! В конце концов, я все-таки прочитал, все, что должен был, назвал файл, как надо, и поместил его в нужное место.
Следует отметить, при этом, что иногда изменение версии NetBeans приводило к тому, что, на какое-то время, все переставало работать - например, менялись какие-то правила для включения или исключения файлов или каталогов в результирующий jar
файл. Но я довольно быстро разбирался с возникшим, скажем так, недопониманием, и все вновь налаживалось. Потом наступил довольно долгий период стабильности. В это время я использовал следующие простые правила:
- называл (речь только про имя) файл, пользуясь рекомендациями из документации к классу
ResourceBundle
- расширение у файла - только
properties
- размещал я этот файл либо в каталоге с исходными текстами (в том, в котором уже лежали какие-нибудь файлы с расширением
java
), либо создавал в структуре каталогов исходных кодов отдельный подкаталог для таких файлов - использовал полное квалифицированное имя для файла, так, как будто это был класс.
Все время, пока я использовал NetBeans, я создавал обычные его проекты - то есть те, которые использовали для сборки, да и для других задач, ant
(с небольшими добавками от самого NetBeans). А тут, в связи с некоторой неопределенностью моих взаимоотношений с NetBeans, я решил попробовать проекты, опирающиеся на maven
. И, конечно же, сразу начались разные интересные заморочки, одна из которых оказалась связанной с файлами properties
, используемых для инициализации PropertiesResourceBundle
. В чем же она заключалась?
Я сделал все, как обычно, разместив properties
файл в каталоге с остальными исходным текстами. Для инициализации объекта, как всегда, использовал getBundle
с указанием полного квалифицированного имени файла. Но, во время тестового запуска, в логи упало сообщение о том, что нужный файл с сообщениями не найден. Я прошелся по коду загрузки properties
файла отладчиком, но лишь убедился в том, что нужный файл не находится. Тогда я просто посмотрел на содержимое jar
файла, получившегося в результате сборки проекта, и понял, что нашел причину - нужный файл с сообщениями отсутствовал.
Так стало понятно, что maven
(возможно, используемый в паре с NetBeans-ом) не ожидает увидеть файлы properties
там, где их ожидал увидеть ant
(в той же самой связке). Но что же тогда надо сделать мне, чтобы maven
перестал капризничать? В принципе, при помощи Google, я довольно быстро нашел ответ. Для начала, мне попалась парочка предложений использовать специальные плагины maven
-а для работы с ресурсными файлами, но потом я нашел вопрос на StackOverflow такого же "страдальца", как я, с той лишь разницей, что он использовал Eclipse, а не NetBeans. Ответ на этот вопрос еще раз доказал две истины: первая заключается в том, что простое решение есть практически всегда, в нашем случае, не надо никаких плагинов, maven
прекрасно работает с ресурсными файлами, если они размещены в правильном каталоге - src/main/resources
, а не src/main/java
; ну а вторая - документацию по используемым инструментам надо читать.
Так как вторую истину я вновь проигнорировал (даже в применении к ответу на найденный вопрос), то и результата удалось добиться не сразу. Сначала я решил, что я - самый умный и решил схитрить. В результате, оказалось, что я - просто самый ленивый. Мне не хотелось воссоздавать в каталоге src/main/resources
те же структуры подкаталогов, что и в src/main/java
(хотя в ответе на вопрос было прямое указание на это), поэтому ресурсные файлы я перенес с переименованием. Пример моей дурости: файл messages.properties
лежал в подкаталоге com/mydomain/myproduct/mymodule
и я решил, что если, при переносе, назову его com.mydomain.myproduct.mymodule.messages.properties
, то волшебным образом все заработает. Не заработало. Поэтому пришлось в каталоге src/main/resources
дублировать структуру подкаталогов, что, конечно же, совершенно не сложно. После того, как я сделал все, как надо, файлы с ресурсными строками вновь стали помещаться в нужные jar
файлы и работоспособность ПО была восстановлена.
Какова же мораль? Как я и писал в самом начале, если вы понимаете суть процесса, знаете, какой именно, после всех ваших действий, должен быть получен результат, то смена одного инструмента на другой не должна представлять серьезных проблем. В моем случае, ant
понимал нахождение вспомогательных (не файлов с исходным кодом java
) в одних каталогах, а maven
требовал помещение этих файлов в другие файловые структуры. Но зная, что вспомогательные файлы, в конце концов, должны оказаться в собранных jar
-ах, да и понимая, что maven
и с файлами, содержащими исходный код, работает несколько иначе, я довольно быстро нашел и саму проблему и ее решение (да, с помощью Google, но ведь известно, что в правильно поставленном вопросе уже содержится 80% ответа). Так что, знание - сила.
Вот, пожалуй, и все, что я хотел сегодня рассказать. На самом деле, у ResourceBundle
есть много всяких интересных особенностей, некоторыми из которых я пользуюсь. Возможно, я расскажу и о них как-нибудь, а пока - совет: читайте документацию.