Как стать автором
Обновить

Конфигурирование J2SE и J2EE приложений: стандартные способы и их альтернативы

Время на прочтение 8 мин
Количество просмотров 13K
В наше время существует множество вариантов построения Java-приложений и задачи их конфигурирования решаются по разному.
В статье будут рассмотрены техники и особенности конфигурирования J2SE и J2EE приложений с применением стандартных средств JDK, а также альтернативы этим способам.


J2SE


java.util.Properties

Классический вариант конфигурирования приложений — применение класса java.util.Properties. Внутри все очень просто — по сути это расширение интерфейса java.util.Map с возможностью сохранения и инициализации значений по-умолчанию.

Минусов несколько:
  • Отсутствует типизация — getProperty возвращает только String;
  • Отслеживание изменений в файле конфигурации не поддерживается (т.е. при изменении никаких событий порождено не будет и приложение ничего о них не узнает);
  • Работа только с одним файлом. N файлов = N экземпляров Properties.


Данный перечень минусов говорит о том, что применение чистого Properties в реальном продакшене неэффективно. Первое с чем можно столкнуться при таком подходе — нареканиями от службы эксплуатации по поводу того, что внесение любых изменений в конфигурацию требует перезапуска приложения.
Наиболее вероятные, по моему мнению, негативные следствия из этого:
  • Эксплуатация сведет к минимуму все изменения, даже ценой наличия некоторого процента ошибок в обработке обращений. Все зависит от интенсивности обращений к приложению, доходности каждого из них и соотношения этих чисел с процентом ошибочных обращений;
  • Вина за первый пункт будет на разработчике — и правильно, т.к. приложения должны делаться так, чтобы их можно было эффективно использовать;
  • Потери от простоя тоже будут относиться на кривизну ПО, а значит относиться и к разработчику этого ПО;
  • Рост чувства вины и постепенная потеря авторитета у службы эксплуатации;
  • Рост негативного эмоционального фона


На все это можно возразить: «Можно же просто добавить перечитывание файла настроек и подсистему генерации событий» — да, это так, только все это уже сделано и сделано до мелочей, которые кажутся неочевидными сейчас, но всегда обязательно проявляются.
В следующей статье я расскажу о Commons Configuration и том, как обозначенные выше проблемы там решаются.
А пока — рассмотрим типовые варианты конфигурирования J2EE-приложений.

J2EE-EJB3


Инжекция ресурса

Один из наиболее простых вариантов конфигурирования EJB-приложений заключается в использовании дескриптора развертывания (ejb-jar.xml):

<enterprise-beans>
<session>
<ejb-name>MyService</ejb-name>
<env-entry>
<env-entry-name>myProperty</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>100500</env-entry-value>
</env-entry>
</session>
</enterprise-beans>



В дескрипторе мы указываем имя (env-entry-name), тип (env-entry-type) и значение параметра (env-entry-value), после чего производим его инжекцию при помощи аннотации Resource:
@Resource(name = "myProperty")
private String myProperty;
@PostConstruct
public void postConstruct() {
System.out.println("myProperty = " + myProperty);
}



Данный подход позволяет работать с параметрами следующих типов:
  • java.lang.String
  • java.lang.Boolean
  • java.lang.Byte
  • java.lang.Character
  • java.lang.Double
  • java.lang.Float
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Short


К минусам стоит отнести то, что изменение параметров приводит к редеплою приложения, что, в свою очередь, приводит к некоторому периоду неработоспособности.
Политика редеплоя зависит от настроек сканера изменений дескрипторов приложений на сервере приложений.
Так, например, в случае с сервером приложений JBoss 5.x-6.x изменение ejb-jar.xml гарантированно приводит к редеплою (если, конечно, сканер не выключен и редеплой производится вручную, через JMX-консоль).

Использование внешнего файла настроек


Существует очень полезный документ — ограничения технологии EJB. В этом документе есть четкие показания к неиспользованию файловых ресурсов. Показания следующие:
  • Файловый ресурс не является транзакционным;
  • Файлы не являеются подходящим местом для хранения бизнес-данных, поскольку существуют вне компетенции сервера приложений и не обеспечивают должного уровня поддержки механизмов блокировок.


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

Таким образом мы приходим к классическому варианту использования java.util.Properties внутри EE-приложений:
@Stateless(mappedName = "BackendService")
public class BackendServiceBean implements BackendServiceLocal {

  private static final String P_PROPERTIES = "myProperties.properties";

  private static final Logger logger = LoggerFactory.getLogger(BackendServiceBean.class);

  @EJB
  private DataRepositoryLocal dataRepository;

  @Resource(name = "remoteURL")
  private String remoteURL;

  private Properties properties;

  @PostConstruct
  private void init(){
    InputStream propertiesResource = null;
    try {
      propertiesResource = getClass().getResourceAsStream(P_PROPERTIES);
      properties = new Properties();
      properties.load(propertiesResource);
    } catch (Exception e) {
      logger.error("Error", e);
    }finally {
      if (propertiesResource !=null){
        try {
          propertiesResource.close();
        } catch (Exception e) {
          logger.error("Error", e);
        }
      }
    }
  }

  public Properties getProperties() {
    return properties;
  }



Минусы те-же, что и обозначенные ранее у J2SE и java.util.Properties. Плюс к этому — мы находимся в контексте J2EE и не можем просто добавить некий поток, отслеживающий изменения файлов и генерирующий события (т.к. потоки в J2EE-приложении создавать нельзя).
Кто-то может сказать: «Надо перечитывать .properties файл каждый раз, когда приложение вызывает getProperty у нашего properties-proxy-объекта». Да, это можно сделать, но в этом случае следует забыть о высокой производительности приложения — отрытие файла на чтение, его загрузка в буфер, парсинг и создание экземпляра Properties будет вносить ощутимую задержку в обработку.
Правильный вариант применения такого решения — хранение исключительно статических read-only настроек.

Другие варианты


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

Для J2EE приложений такими вариантами могут быть:
  • Получение настроек из БД (причем занесением в БД занимается другое приложение — например админка-конфигуратор);
  • Запрос настроек у удаленного компонента-поставщика (например с именем ConfigurationProvider).


Как для J2EE, так и для J2SE-приложений можно применять различные фреймворки/создавать свои собственные, заточенные под решение задач конфигурирования.

J2EE-Servlets


При конфигурировании сервлетов мы имеем дело с дескриптором web.xml, в котором задаются необходимые нам параметры:
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee java.sun.com/xml/ns/j2ee/web-app_2_5.xsd">

  <!-- Контекстные параметры -->
  <context-param>
    <param-name>myContextParam1</param-name>
    <param-value>value1</param-value>
  </context-param>
  <context-param>
    <param-name>myContextParam2</param-name>
    <param-value>value2</param-value>
  </context-param>
  
  <!-- Параметры Фильтров -->
  <filter>
    <filter-name>myFilter</filter-name>
    <filter-class>net.universe.filter.EntityAccessFilter</filter-class>
    <init-param>
      <param-name>checkType</param-name>
      <param-value>ldap</param-value>
    </init-param>
    <init-param>
      <param-name>myParam</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  
  <!-- Параметры Сервлетов -->
  <servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>net.universe.servlet.MyServlet</servlet-class>
    <init-param>
      <param-name>servletParam1</param-name>
      <param-value>paramValue1</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
</web-app>



Настройка заключается в изменении элементов конфигурации param-value. После изменений и сохранения файла у нас также происходит редеплой приложения с последующим периодом его неработоспособности. Этот способ конфигурирования, как и вариант с ejb-jar.xml, наиболее подходит для параметров, которые не предполагается изменять по ходу работы приложения. Техники по работе с runtime-настройками здесь аналогичны применяемым в случае с EJB — базы данных, JNDI и т.п…

Общее для всех


System.getProperty

Общим, для всех перечисленных способов конфигурирования, является применение системных параметров передающихся в строке запуска Java-приложения:
java -DmyProperty1=myPropertyValue1 -DmyProperty2=myPropertyValue2 -jar myapp.jar


После этого параметры конфигурации можно взять при помощи класса java.lang.System:
String string = System.getProperty("myProperty1");


Данный способ ограниченно применим в контексте работы с J2EE — при работе в кластерном режиме системная переменная может не приехать на все узлы. Почему «может» — потому что, например, у сервера приложений JBoss есть служба SystemPropertiesService и фрагменты по ее конфигурированию можно включить в наше EE-приложение (т.е. в итоге «системная» переменная окажется на всех узлах, т.к. будет в конфигурационных файлах в составе приложения).

Довольно часто такой способ конфигурирования применяется при необходимости быстро добавить какие-то новые проверки-условия в код.
Например настолько быстро, что время есть только на добавление условий, компиляцию класса и замену его в уже развернутом EE-приложении/библиотеке с последующим редеплоем/перезапуском.
Конечно, такой вариант нельзя назвать хорошей практикой, но тем не менее такое случается в реальной жизни.

Также можно отметить альтернативу такому подходу — применение аспектно-ориентированного программирования/инъекций в байт-код. Эти техники позволяют оставить неизменным исходное приложение, но требуют более высокого уровня квалификации разработчика, особенно когда речь идет о динамическом внедрении AOP-перехватчиков (interceptors) на работающей продакшен системе.

JMX


JMX является удобным средством много для чего, в том числе и для конфигурирования приложений. Можно комбинировать применение java.util.Properties/Commons Configuration и выставленного MBean-а с методами установки/получения значений наших параметров (при установке — с последующим делегированием к properties.setProperty).
Подобное решение может успешно применяться там, где нет доступа к файлам конфигурации, но есть доступ к MBeanServer-у по сети.

Минусы у такого подхода следующие:
  • JMX подсистема в J2SE приложениях по-умолчанию выключена;
  • Допустимо применение только простых типов параметров (сложные тоже можно, но тогда управлять через jconsole уже не получится);
  • В контексте J2EE работа с JMX может приобретать весьма замысловатые формы. Так, например, микроядро JBoss 4.x-6.x использует в своей основе JMX и попытка получить дерево MBean-ов в утилите jconsole приведет, с высокой долей вероятности, к ее зависанию/очень медленной работе.


На этом пока все.

В ближайшее время будет завершена статья о библиотеке Commons Configuration, которая значительно расширяет возможности по работе с конфигурационными файлами в J2SE и J2EE приложениях.

Спасибо за внимание!
Теги:
Хабы:
+17
Комментарии 14
Комментарии Комментарии 14

Публикации

Истории

Работа

Java разработчик
342 вакансии

Ближайшие события

PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн
Weekend Offer в AliExpress
Дата 20 – 21 апреля
Время 10:00 – 20:00
Место
Онлайн