Вступление
Одной из приятных недавних особенностей Spring (эпоха 2.x) является поддержка пользовательского XML. Это способ, которым Spring сам добавил все виды новых тегов, таких как <util:list>
и <mvc:annotation-driven>
. Это работает довольно элегантно, и представляет собой интересную альтернативу для настройки Java с использованием XML, особенно если приложение уже использует Spring.
Я написал пример приложения, чтобы попытаться дать легко копируемый пример того, как это делается. В примере используются Spring и пользовательский анализатор XML для создания динамических меню Swing. Это хорошее сравнение с созданием динамических меню Swing с использованием версии Digester, которую я опубликовал некоторое время назад.
Конечно, это не очень хороший способ создания меню Java в целом! В большинстве приложений это может быть примером мягкого кодирования . Это действительно имеет смысл только в приложении, где действительно важно иметь возможность добавлять или удалять меню без изменения кода Java. Так что относитесь к этому как к хорошему примеру, но, пожалуйста, не начинайте создавать свой GUI таким образом.
Spring Custom XML
Пользовательский XML работает в файле конфигурации Spring, потому что Spring может динамически проверять и анализировать XML. Чтобы сделать это, Spring сначала должен иметь возможность проверить XML, который он анализирует по схеме. Это делается путем поиска всех файлов в вызываемом classpath META-INF/spring.schemas
. Эти файлы обеспечивают местоположение в пути к классам для схемы XML, которая идет с данным пространством имен. Например, «основной» XML для Spring определен в beans
пространстве имен. META-INF/spring.schemas
Файл в spring-beans
JAR имеет записи , как это:
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
Поэтому, когда мы используем beans
схему в нашем Spring XML, он знает, где на пути к классам искать схему, чтобы он мог проверить этот XML.
После проверки схемы Spring должен найти «обработчик», который знает, как создавать бины Spring на основе XML. Spring находит обработчики, просматривая все файлы в вызываемом classpath META-INF/spring.handlers
. spring.handlers
Файл в spring-beans
JAR имеет записи , как это:
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
Задача обработчика — создавать определения bean-компонентов, а не обычные объекты Java, которые будут жить как bean-компоненты в контексте приложения Spring. Это связано с тем, что Spring по-прежнему должен управлять такими вещами, как bean-компоненты, в зависимости от других bean-компонентов, что означает, что Spring должен проанализировать весь XML-код, чтобы выяснить граф зависимостей, прежде чем создавать какие-либо объекты.
Пример приложения
Наш пример приложения состоит из нескольких частей:
spring.schemas
Иspring.handlers
файлы вMETA-INF
.- Схема XML, определяющая, что является допустимым в нашем пользовательском пространстве имен.
MenuNamespaceHandler
класс записи, который позволяет нам регистрировать, какие XML-элементы идут с какими классами синтаксического анализатора.MenuDefinitionParser
фактический анализатор XML для нашего пользовательского пространства имен XML.- Обычный конфигурационный файл Spring XML, который также включает в себя наш собственный XML.
- Основной класс, чтобы начать все это.
Также есть Java-класс, MenuItem
который мы используем для хранения идентификатора, заголовка и любых дочерних элементов пункта меню. Он ничего не знает о Spring или XML; это просто POJO.
Определение пользовательского XML
spring.schemas
Файл довольно просто. Обратите внимание, что это соответствует файлу на пути к классам; Spring не будет искать в Интернете вашу XML-схему во время выполнения.
http\://anvard.org/springxml/menu.xsd=org/anvard/springxml/menu.xsd
spring.handlers
Файл также довольно просто. Это просто указывает на правильный класс обработчика:
http\://anvard.org/springxml/menu=org.anvard.springxml.MenuNamespaceHandler
Схема XML здесь опущена; это схема XML, и не нужно много говорить. Следует отметить, что он допускает произвольное вложение <menu>
элементов внутри других <menu>
элементов.
Еще один кусок шаблона; обработчик пространства имен. Поскольку наше пространство имен действительно простое и содержит только один элемент верхнего уровня ( menu
), это одна строка:
public void init() { registerBeanDefinitionParser("menu", new MenuDefinitionParser()); }
Парсер, где это становится интересным. Парсер будет вызываться, когда Spring читает XML-файл, когда он сталкивается с элементом, который принадлежит соответствующему пространству имен. Однако он будет вызываться только для элемента верхнего уровня; мы должны обрабатывать любые вложенные элементы по мере необходимости.
protected AbstractBeanDefinition parseInternal(Element element, ParserContext context) { BeanDefinitionBuilder builder = parseItem(element); List<Element> childElements = DomUtils.getChildElementsByTagName( element, "menu"); if (null != childElements && childElements.size() > 0) { ManagedList<AbstractBeanDefinition> children = new ManagedList<>( childElements.size()); for (Element child : childElements) { children.add(parseInternal(child, context)); } builder.addPropertyValue("children", children); } return builder.getBeanDefinition(); } private BeanDefinitionBuilder parseItem(Element element) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .rootBeanDefinition(MenuItem.class); String id = element.getAttribute("id"); if (StringUtils.hasText(id)) { builder.addPropertyValue("id", id); } String title = element.getAttribute("title"); if (StringUtils.hasText(title)) { builder.addPropertyValue("title", title); } String listener = element.getAttribute("listener"); if (StringUtils.hasText(listener)) { builder.addPropertyReference("listener", listener); } return builder; }
В этом случае, поскольку мы учли идею, что меню может содержать дочерние меню, мы должны обработать это здесь с некоторой рекурсией. Обратите внимание, что для каждого <menu>
элемента любого уровня мы создаем отдельное определение bean-компонента Spring (это одна из целей rootBeanDefinition()
статического вызова метода). Действительно важно отметить, что при построении определения компонента мы не создаем MenuItem
объект напрямую и не устанавливаем никаких свойств напрямую. Фактически, в случае children
свойства мы даже не строим список правильного типа, так как MenuItem
класс ожидает получить список MenuItem
потомков, но мы строим список AbstractBeanDefinition
. Пружина обрабатывает всю необходимую проводку, когда она на самом деле создает наши MenuList
объекты, включая поиск каждой из ссылок в списке и заполнение нового списка реальными объектами.
Еще одна вещь, которая немного сбивает с толку, это то, что ссылка на один другой бин Spring использует addPropertyReference()
, тогда как используется управляемый список определений бина Spring addPropertyValue()
.
Использование собственного XML
Теперь, когда эти элементы установлены, мы можем использовать пользовательский XML точно так же, как любой другой XML в файле конфигурации Spring. Например:
<?xml version="1.0" encoding="UTF-8"?> <bean:beans xmlns="http://anvard.org/springxml/menu" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bean="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://anvard.org/springxml/menu http://anvard.org/springxml/menu.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <bean:bean id="simpleListener" class="org.anvard.springxml.SimpleMenuItemListener" /> <menu id="menu1" title="Parent 1"> <menu id="menu2" title="Child 1" listener="simpleListener" /> <menu id="menu3" title="Child 2" listener="simpleListener" /> <menu id="menu4" title="Child 3" listener="simpleListener" /> <menu id="menu5" title="Child 4" listener="simpleListener" /> </menu> <menu id="menu6" title="Item 1" listener="simpleListener" /> <menu id="menu7" title="Grand Parent 1"> <menu id="menu8" title="Parent 2"> <menu id="menu9" title="Child 5" listener="simpleListener" /> </menu> </menu> <util:list id="toplevel"> <bean:ref bean="menu1" /> <bean:ref bean="menu6" /> <bean:ref bean="menu7" /> </util:list> </bean:beans>
Обратите внимание, что мы можем сделать наш пользовательский XML пространством имен по умолчанию, поэтому нам не нужно добавлять префикс наших XML-элементов; мы также можем сделать bean
пространство имен по умолчанию, как это более типично в конфигурационном файле Spring XML. Мы можем свободно смешивать наш собственный XML со стандартным Spring XML.
Также обратите внимание, что наш пользовательский XML может делать ссылки обратно на обычные бины Spring, если мы делаем правильные вещи в нашем парсере, чтобы сделать эту работу.
Мы используем список, называемый toplevel
удобным способом поиска наиболее удаленных пунктов меню для нашей строки меню. После анализа XML все бины загружаются в контекст приложения Spring, и структура XML больше не применяется.
Использование этого файла из нашего основного класса выглядит так же, как любой код Spring:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/menuDefinition.xml");
Все наши пункты меню доступны в контексте приложения Spring, поэтому мы могли бы ctx.getBean("menu9")
вернуться и вернуть пункт меню с заголовком «Child 5».
Вывод
Несмотря на то, что многие пользователи Spring переходят на конфигурацию на основе аннотаций, в XML все еще есть вещи, которые проще сделать, например, создать множество экземпляров класса с различными свойствами. Настраиваемое пространство имен XML — это способ сделать конфигурацию Spring XML более компактной и удобочитаемой.