Статьи

Меню с Apache Digester

Пример парсинга Digester XML

Apache Digester — почтенная библиотека для анализа XML и превращения его в граф Java-объектов. Существует множество библиотек для анализа и написания XML, и Digester не самый производительный во время выполнения, он не поддерживает повторную запись XML и может показаться немного недоступным для новых пользователей. Но мне это очень нравится, и это очень мощный инструмент в моем наборе инструментов.

Конфигурация XML

Существует особый вариант использования XML, где XML используется в качестве редактируемого человеком файла конфигурации. Конечно, файлы свойств просты в использовании для этой цели, но там, где требуется более сложная конфигурация, особенно с естественной иерархией конфигурации, XML просто кажется более логичным. Например, сравните простой log4j.properties файл с простым log4j.xml файлом.

log4j.rootLogger=INFO,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %c{4} - %m%n
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="A1" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out"/>
    <param name="Threshold" value="INFO"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%-4r %-5p [%t] %c{4} - %m%n"/>
    </layout>
  </appender>
  <root>
    <appender-ref ref="CONSOLE"/>
  </root>
</log4j>

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

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

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

Дайджест для меню

Недавно у меня возникла необходимость предоставить динамическое меню Swing. Пункты меню будут настраиваться после поставки программного обеспечения, и я не хочу, чтобы эта настройка включала язык программирования общего назначения. Вероятно, есть какая-то библиотека для динамических меню Swing, но я не хотел находить и интегрировать еще одну стороннюю библиотеку для чего-то, что будет содержать менее 250 строк кода. Таким образом, XML-меню и Digester были логичным соответствием. В Digester мой подход всегда состоит в том, чтобы сделать пример того, как я хочу, чтобы XML выглядел. Я хотел произвольное вложение меню, и пункты меню просто должны были отправлять сообщение при выборе, поэтому результат был довольно простым, похожим на этот:

<?xml version="1.0"?>
<menus>
  <menu id="1" title="Parent">
    <menu id="2" title="Child" />
    <menu id="3" title="Second Child" />
  </menu>
  <menu id="4" title="Menu" />
</menus>

Следующий шаг с Digester — предоставить ему правила для сопоставления XML с Java. Digester поддерживает аннотации и предоставляет «свободный» API для создания правил в Java, но я предпочитаю набор правил XML. Вот где светится Digester, так как правила для этого примера очень просты:

<?xml version="1.0"?>
<digester-rules>
  <pattern value="*/menu">
    <object-create-rule classname="org.anvard.digester.MenuItem" />
    <set-properties-rule/>
    <set-next-rule methodname="add"/>
  </pattern>
</digester-rules>

Класс MenuItem является POJO с добытчиками и сеттеров для idи titleатрибутов. Он также хранит список своих дочерних элементов и предоставляет метод add.

Шаблон определяет набор действий, которые происходят всякий раз, когда он находит элемент меню на любом уровне. object-create-ruleГоворит Digester для создания экземпляра MenuItem. Этот новый объект помещается в стек, поэтому он становится целью по умолчанию для следующих правил. set-properties-ruleГоворит Digester для соответствия атрибутов Java свойств. set-next-ruleГоворит Digester передать текущую вершину стека в качестве параметра объекта , который находится рядом в стеке вызова метода add. Наконец, когда </pattern>достигается закрывающий тег, новый объект выталкивается из стека.

Чтобы использовать это из Java это вопрос нескольких строк.

List<MenuItem> menus = new ArrayList<MenuItem>();
File file = new File("/some/directory/menu-config.xml");
Digester digester =
  DigesterLoader.createDigester(MenuManager.class
    .getResource("/menu-rules.xml"));
digester.push(menus);
try {
    digester.parse(file);
} catch (IOException e) {
    LOG.warn("Could not load file", e);
} catch (SAXException e) {
    LOG.warn("Invalid file", e);
}

В этом коде используется тот факт, что в Listклассе есть метод add, который принимает один параметр. Поскольку список помещается в стек до того, как Digester анализирует файл, он служит родителем для всех пунктов меню на верхнем уровне. Это избавляет нас от необходимости писать правила Digester для <menus>элемента.

Окончательная версия, которую я написал, позволила интегрировать предоставляемые XML-меню с существующими жестко запрограммированными меню и, конечно, создавать реальные JMenuItemэкземпляры с прослушивателями действий.

Почему Дигестер, Плюс Альтернативы

Дигестер использует SAX под одеялами. Использование SAX напрямую, даже для этого простого примера, было бы болезненным. Чем меньше об этом сказано, тем лучше.

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

Среда Spring с поддержкой пользовательских пространств имен XML теперь является еще одной хорошей альтернативой Digester. У меня был успех с пользовательским XML для Spring, и если бы конкретный Java-код, с которым я работал, уже использовал Spring, я бы, наверное, выбрал его. Но для проектов, которые еще не используют Spring, кажется, что тяжеловесно тянуть его и писать собственные обработчики пространства имен для чтения пары файлов. Кроме того, если непрограммисты собираются редактировать файл, они должны знать, не трогать шаблон сверху.

С другой стороны, мой опыт работы в команде заключается в том, что новым людям может потребоваться немного времени, чтобы понять, что происходит с Дигестером. Я видел, как некоторые из них продолжают использовать это сами, что, я думаю, говорит о хорошем. Он имеет сходство со средой Spring в том смысле, что вам нужно привыкнуть к размышлениям и самоанализу, чтобы иметь возможность представить, что происходит за кулисами. Насколько мне нравится, язык правил XML также может быть немного сложным для чтения и запоминания.

Digester использовался еще до появления большинства современных инструментов XML, но он все еще поддерживается, и это была очень хорошая библиотека, которую нужно знать.