Начиная с версии 2.0, в Spring появился механизм добавления расширений на основе схем к основному XML-формату Spring для определения и конфигурирования бинов. В этом разделе рассказывается о том, как писать свои собственные синтаксические анализаторы определений бинов на XML и интегрировать такие анализаторы в IoC-контейнер Spring.

Чтобы облегчить создание конфигурационных файлов, использующих XML-редактор с поддержкой схем, расширяемый механизм конфигурации XML в Spring соновывается на XML Schema.

Для создания новых XML-расширений конфигурации:

  1. Создайте XML-схему для описания вашего специально созданного элемента (элементов).

  2. Создайте собственную реализацию NamespaceHandler.

  3. Запишите одну или несколько реализаций BeanDefinitionParser (именно здесь все самое важное и происходит).

  4. Зарегистрируйте новые артефакты в Spring.

Для унифицированного примера мы создаем XML-расширение (специальный элемент XML), которое позволяет нам конфигурировать объекты типа SimpleDateFormat (из пакета java.text). После этого можно задавать определения бина типа SimpleDateFormat следующим образом:

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

(Более подробные примеры приведены далее в этом приложении. Цель этого первого простого примера - провести вас через основные шаги по созданию пользовательского расширения).

Создание схемы

Создание XML-расширения конфигурации для использования с IoC-контейнером Spring начинается с создания XML-схемы для описания расширения. В нашем примере мы используем следующую схему для конфигурирования объектов SimpleDateFormat:

<!-- myns.xsd (внутри пакета org/springframework/samples/xml) -->
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.mycompany.example/schema/myns"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">
    <xsd:import namespace="http://www.springframework.org/schema/beans"/>
    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="lenient" type="xsd:boolean"/>
                    <xsd:attribute name="pattern" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
  1. Указанная строка содержит базу расширения для всех идентифицируемых тегов (то есть у них есть атрибут id, который мы можем использовать в качестве идентификатора бина в контейнере). Мы можем использовать этот атрибут, поскольку импортировали пространство имен beans, предоставленное Spring.

Предыдущая схема позволяет нам конфигурировать объекты SimpleDateFormat непосредственно в XML-файле контекста приложения с помощью элемента <myns:dateformat/>, как показано в следующем примере:

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

Обратите внимание, что после создания классов инфраструктуры, предыдущий фрагмент XML по сути не отличается от следующего фрагмента XML:

<bean id="dateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyy-MM-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

Второй из двух предыдущих фрагментов создает бин в контейнере (идентифицированный именем dateFormat типа SimpleDateFormat) с парой установленных свойств.

Основанный на схемах подход к созданию формата конфигурации обеспечивает тесную интеграцию с IDE, имеющей редактор XML со схемами. Используя правильно составленную схему, вы можете использовать автозаполнение, чтобы позволить пользователю выбирать между несколькими вариантами конфигурации, определенными в перечислении.

Написание кода NamespaceHandler

В дополнение к схеме нам нужен NamespaceHandler для синтаксического разбора всех элементов данного конкретного пространства имен, с которыми Spring сталкивается при анализе конфигурационных файлов. В данном примере NamespaceHandler должен позаботиться о синтаксическом анализе элемента myns:dateformat.

Интерфейс NamespaceHandler включает три метода:

  • init(): Позволяет инициализировать NamespaceHandler и вызывается Spring перед использованием дескриптора.

  • BeanDefinition parse(Element, ParserContext): Вызывается, если Spring обнаруживает элемент верхнего уровня (не вложенный внутрь определения бина или другого пространства имен). Этот метод может сам регистрировать определения бинов, возвращать определение бина или и то, и другое.

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): Вызывается, если Spring обнаруживает атрибут или вложенный элемент другого пространства имен. Декорирование одного или нескольких определений бина используется (например) с областями видимости, которые поддерживает Spring. Начнем с простого примера без использования декорирования, а затем покажем декорирование в несколько более сложном примере.

Хотя вы можете написать свой собственный NamespaceHandler для всего пространства имен (и, следовательно, предоставить код, который анализирует на уровне синтаксиса каждый элемент в пространстве имен), часто бывает так, что каждый элемент XML верхнего уровня в конфигурационном XML-файле в Spring дает в результате одно определение бина (как в нашем случае, когда один элемент <myns:dateformat/> дает в результате одно определение бина SimpleDateFormat). В Spring есть ряд удобных классов, которые поддерживают этот сценарий. В следующем примере мы используем класс NamespaceHandlerSupport:

Java
package org.springframework.samples.xml;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }
}
Kotlin
package org.springframework.samples.xml
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class MyNamespaceHandler : NamespaceHandlerSupport {
    override fun init() {
        registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
    }
}

Вы можете заметить, что в этом классе не так уж много логики синтанксического анализа. Действительно, класс NamespaceHandlerSupport имеет встроенное понятие делегирования. Он поддерживает регистрацию любого количества экземпляров BeanDefinitionParser, которым он делегирует полномочия на выполнение работы, если ему нужно проанализировать элемент в своем пространстве имен. Это чистое разделение сквозной функциональности позволяет NamespaceHandler управлять оркестрацией синтаксического анализа всех пользовательских элементов в своем пространстве имен, делегируя BeanDefinitionParsers выполнение основной работы по синтаксическому анализу XML. Это означает, что каждый BeanDefinitionParser содержит только логику для синтаксического анализа одного специального элемента, как мы увидим в следующем шаге.

Использование BeanDefinitionParser

BeanDefinitionParser используется, если NamespaceHandler обнаруживает элемент XML типа, который был отображен на конкретный синтаксический анализатор определения бинов (в данном случаеdateformat). Другими словами, BeanDefinitionParser отвечает за синтаксический анализ одного отдельного элемента XML верхнего уровня, определенного в схеме. В анализаторе мы получаем доступ к элементу XML (и, соответственно, к его подэлементам), что позволяет произвести синтаксический разбор нашего специального XML-содержимого, как это можете видеть в следующем примере:

Java
package org.springframework.samples.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import java.text.SimpleDateFormat;
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class;
    }
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // никогда не будет null, поскольку схема явно требует, чтобы было указано какое-то значение
        String pattern = element.getAttribute("pattern");
        bean.addConstructorArgValue(pattern);
        // это необязательное свойство
        String lenient = element.getAttribute("lenient");
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }
}
  1. Мы используем AbstractSingleBeanDefinitionParser, предоставленный Spring, для выполнения обработки многих основных операций по созданию одного BeanDefinition.
  2. Мы предоставляем суперклассу AbstractSingleBeanDefinitionParser тип, который представляет наше единственное BeanDefinition.
Kotlin
package org.springframework.samples.xml
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
import org.springframework.util.StringUtils
import org.w3c.dom.Element
import java.text.SimpleDateFormat
class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() {
    override fun getBeanClass(element: Element): Class<*>? {
        return SimpleDateFormat::class.java
    }
    override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
        // никогда не будет null, поскольку схема явно требует, чтобы было указано какое-то значение
        val pattern = element.getAttribute("pattern")
        bean.addConstructorArgValue(pattern)
        // это необязательное свойство
        val lenient = element.getAttribute("lenient")
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
        }
    }
}
  1. Мы используем AbstractSingleBeanDefinitionParser, предоставленный Spring, для выполнения обработки многих основных операций по созданию одного BeanDefinition.
  2. Мы предоставляем суперклассу AbstractSingleBeanDefinitionParser тип, который представляет наше единственное BeanDefinition.

В данном простом примере это все, что нам нужно сделать. Создание нашего единственного BeanDefinition обрабатывается суперклассом AbstractSingleBeanDefinitionParser, как и извлечение и установка уникального идентификатора определения бина.

Регистрация обработчика и схемы

Написание кода завершено. Все, что осталось сделать, это заставить инфраструктуру синтаксического анализа Spring XML узнать о нашем специальном элементе. Для этого мы регистрируем наш специальный namespaceHandler и пользовательский XSD-файл в двух специально предназначенных для этого файлах свойств. Эти файлы свойств можно как поместить в каталог META-INF в вашем приложении, так и, например, распространить вместе с вашими бинарными классами в JAR-файле. Инфраструктура аналитического анализа Spring XML автоматически подхватывает ваше новое расширение, используя эти специальные файлы свойств, форматы которых подробно описаны в следующих двух разделах.

Написание META-INF/spring.handlers

Файл свойств spring.handlers содержит отображение URI-идентификаторов XML-схемы на классы дексрипторов пространства имен. В рамках нашего примера нам нужно написать следующее:

http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler

(Символ : является допустимым разделителем в формате свойств Java, поэтому символ : в URI-идентификаторе необходимо экранировать обратной косой чертой).

Первая часть (ключ) пары ключ-значение – это URI-идентификатор, связанный с вашим специальным расширением пространства имен, и он должен точно соответствовать значению атрибута targetNamespace, как указано в вашей специальной XSD-схеме.

Написание 'META-INF/spring.schemas'

Файл свойств spring.schemas содержит отображение местоположений XML-схемы (на которые ссылаются вместе с объявлением схемы в XML-файлах, использующих схему в виде атрибута xsi:schemaLocation) на ресурсы пути классов. Этот файл необходим для того, чтобы Spring не пришлось использовать стандартный EntityResolver, который требует доступа в Интернет для получения файла схемы. Если вы укажите отображение в этом файле свойств, Spring будет искать схему (в данном случае myns.xsd в пакете org.springframework.samples.xml) в пути классов. В следующем фрагменте показана строка, которую нужно добавить для нашей специальной схемы:

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

(Помните, что символ : должен быть экранирован).

Рекомендуется развернуть ваш XSD-файл (или файлы) прямо рядом с классами NamespaceHandler и BeanDefinitionParser в пути классов.

Использование специального расширения в конфигурации XML в Spring

Использование специального расширения, которое вы сами реализовали, ничем не отличается от использования одного из "специальных" расширений, которые предоставляет Spring. Следующий пример использует специальный элемент <dateformat/>, разработанный в предыдущих шагах, в конфигурационном XML-файле в Spring:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:myns="http://www.mycompany.example/schema/myns"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
    <!-- как высокоуровневый бин -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
    <bean id="jobDetailTemplate" abstract="true">
        <property name="dateFormat">
            <!-- как внутренний бин -->
            <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
        </property>
    </bean>
</beans>
  1. Наш специальный бин.

Более подробные примеры

В этом разделе представлены некоторые более подробные примеры специальных XML-расширений.

Вложение специальных элементов в специальные элементы

В примере, представленном в этом разделе, показано, как писать различные артефакты, необходимые для достижения цели следующей конфигурации:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:foo="http://www.foo.example/schema/component"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">
    <foo:component id="bionic-family" name="Bionic-1">
        <foo:component name="Mother-1">
            <foo:component name="Karate-1"/>
            <foo:component name="Sport-1"/>
        </foo:component>
        <foo:component name="Rock-1"/>
    </foo:component>
</beans>

В предыдущей конфигурации специальные расширения вложены друг в друга. Класс, который фактически конфигурируется элементом <foo:component/>, – это класс Component (показан в следующем примере). Обратите внимание, что класс Component не предоставляет сеттер для свойства components. Это затрудняет (или, скорее, делает невозможным) конфигурирование определения бина для класса Component с помощью внедрения сеттера. В следующем листинге показан класс Component:

Java
package com.foo;
import java.util.ArrayList;
import java.util.List;
public class Component {
    private String name;
    private List<Component> components = new ArrayList<Component> ();
    // ммм, нет сеттера для "components"
    public void addComponent(Component component) {
        this.components.add(component);
    }
    public List<Component> getComponents() {
        return components;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
Kotlin
package com.foo
import java.util.ArrayList
class Component {
    var name: String? = null
    private val components = ArrayList<Component>()
    // ммм, нет сеттера для "components"
    fun addComponent(component: Component) {
        this.components.add(component)
    }
    fun getComponents(): List<Component> {
        return components
    }
}

Типичным решением этой проблемы является создание специального FactoryBean, который открывает свойство сеттера для свойства components. В следующем листинге показан такой специальный FactoryBean:

Java
package com.foo;
import org.springframework.beans.factory.FactoryBean;
import java.util.List;
public class ComponentFactoryBean implements FactoryBean<Component> {
    private Component parent;
    private List<Component> children;
    public void setParent(Component parent) {
        this.parent = parent;
    }
    public void setChildren(List<Component> children) {
        this.children = children;
    }
    public Component getObject() throws Exception {
        if (this.children != null && this.children.size() > 0) {
            for (Component child : children) {
                this.parent.addComponent(child);
            }
        }
        return this.parent;
    }
    public Class<Component> getObjectType() {
        return Component.class;
    }
    public boolean isSingleton() {
        return true;
    }
}
Kotlin
package com.foo
import org.springframework.beans.factory.FactoryBean
import org.springframework.stereotype.Component
class ComponentFactoryBean : FactoryBean<Component> {
    private var parent: Component? = null
    private var children: List<Component>? = null
    fun setParent(parent: Component) {
        this.parent = parent
    }
    fun setChildren(children: List<Component>) {
        this.children = children
    }
    override fun getObject(): Component? {
        if (this.children != null && this.children!!.isNotEmpty()) {
            for (child in children!!) {
                this.parent!!.addComponent(child)
            }
        }
        return this.parent
    }
    override fun getObjectType(): Class<Component>? {
        return Component::class.java
    }
    override fun isSingleton(): Boolean {
        return true
    }
}

Работает хорошо, но открывает конечному пользователю слишком много низкоуровневых классов Spring. Мы собираемся написать специальное расширение, которое спрячет все эти низкоуровневые классы Spring. Если придерживаться описанных ранее шагов, то начинаем с создания XSD-схемы для определения структуры нашего специального тега, как показано в следующем листинге:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/component"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/component"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">
    <xsd:element name="component">
        <xsd:complexType>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:element ref="component"/>
            </xsd:choice>
            <xsd:attribute name="id" type="xsd:ID"/>
            <xsd:attribute name="name" use="required" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

Снова следуя <описанной ранее процедуре, создаем специальный NamespaceHandler:

Java
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
    }
}
Kotlin
package com.foo
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class ComponentNamespaceHandler : NamespaceHandlerSupport() {
    override fun init() {
        registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
    }
}

Далее следует специальный анализатор BeanDefinitionParser. Помните, что мы создаем BeanDefinition, который описывает ComponentFactoryBean. В следующем листинге показана наша специальная реализация BeanDefinitionParser:

Java
package com.foo;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.List;
public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        return parseComponentElement(element);
    }
    private static AbstractBeanDefinition parseComponentElement(Element element) {
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
        factory.addPropertyValue("parent", parseComponent(element));
        List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
        if (childElements != null && childElements.size() > 0) {
            parseChildComponents(childElements, factory);
        }
        return factory.getBeanDefinition();
    }
    private static BeanDefinition parseComponent(Element element) {
        BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
        component.addPropertyValue("name", element.getAttribute("name"));
        return component.getBeanDefinition();
    }
    private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
        ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
        for (Element element : childElements) {
            children.add(parseComponentElement(element));
        }
        factory.addPropertyValue("children", children);
    }
}
Kotlin
package com.foo
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.ManagedList
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import org.springframework.beans.factory.xml.ParserContext
import org.springframework.util.xml.DomUtils
import org.w3c.dom.Element
import java.util.List
class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {
    override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
        return parseComponentElement(element)
    }
    private fun parseComponentElement(element: Element): AbstractBeanDefinition {
        val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
        factory.addPropertyValue("parent", parseComponent(element))
        val childElements = DomUtils.getChildElementsByTagName(element, "component")
        if (childElements != null && childElements.size > 0) {
            parseChildComponents(childElements, factory)
        }
        return factory.getBeanDefinition()
    }
    private fun parseComponent(element: Element): BeanDefinition {
        val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
        component.addPropertyValue("name", element.getAttribute("name"))
        return component.beanDefinition
    }
    private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
        val children = ManagedList<BeanDefinition>(childElements.size)
        for (element in childElements) {
            children.add(parseComponentElement(element))
        }
        factory.addPropertyValue("children", children)
    }
}

Наконец, различные артефакты необходимо зарегистрировать в инфраструктуре Spring XML путем изменения файлов META-INF/spring.handlers и META-INF/spring.schemas следующим образом:

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd

Специальные атрибуты для "обычных" элементов

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

В качестве иного примера, предположим, что вы задаете определение бина для объекта-службы, который (без его ведома) обращается к объединенному в кластер JCache, и вам нужно убедиться, что названный экземпляр JCache запускается без задержек в окружающем кластере. В следующем листинге приведено такое определение:

<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- другие зависимости здесь...  -->
</bean>

Затем можно создать еще одно BeanDefinition, когда будет синтаксически разобран атрибут 'jcache:cache-name'. Затем это BeanDefinition инициализирует именованный JCache. Также можно изменить существующее BeanDefinition для 'checkingAccountService' таким образом, чтобы оно зависело от этого нового JCache-инициализирующего BeanDefinition. В следующем листинге показан наш JCacheInitializer:

Java
package com.foo;
public class JCacheInitializer {
    private String name;
    public JCacheInitializer(String name) {
        this.name = name;
    }
    public void initialize() {
        // множество вызовов API-интерфейса JCache для инициализации именованного кэша...
    }
}
Kotlin
package com.foo
class JCacheInitializer(private val name: String) {
    fun initialize() {
        // множество вызовов API-интерфейса JCache для инициализации именованного кэша...
    }
}

Теперь можно перейти к специализированному расширению. Во-первых, нам необходимо создать XSD-схему, описывающую специализированный атрибут, следующим образом:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/jcache"
        elementFormDefault="qualified">
    <xsd:attribute name="cache-name" type="xsd:string"/>
</xsd:schema>

Далее нужно создать связанный с ним NamespaceHandler, как показано ниже:

Java
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
            new JCacheInitializingBeanDefinitionDecorator());
    }
}
Kotlin
package com.foo
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class JCacheNamespaceHandler : NamespaceHandlerSupport() {
    override fun init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
                JCacheInitializingBeanDefinitionDecorator())
    }
}

Далее нам нужно создать синтаксический анализатор. Обратите внимание, что в данном случае, поскольку мы собираемся произвести синтаксический анализ XML-атрибута, мы пишем BeanDefinitionDecorator, а не BeanDefinitionParser. В следующем листинге показана наша реализация BeanDefinitionDecorator:

Java
package com.foo;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
            ParserContext ctx) {
        String initializerBeanName = registerJCacheInitializer(source, ctx);
        createDependencyOnJCacheInitializer(holder, initializerBeanName);
        return holder;
    }
    private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
            String initializerBeanName) {
        AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
        String[] dependsOn = definition.getDependsOn();
        if (dependsOn == null) {
            dependsOn = new String[]{initializerBeanName};
        } else {
            List dependencies = new ArrayList(Arrays.asList(dependsOn));
            dependencies.add(initializerBeanName);
            dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
        }
        definition.setDependsOn(dependsOn);
    }
    private String registerJCacheInitializer(Node source, ParserContext ctx) {
        String cacheName = ((Attr) source).getValue();
        String beanName = cacheName + "-initializer";
        if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
            BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
            initializer.addConstructorArg(cacheName);
            ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
        }
        return beanName;
    }
}
Kotlin
package com.foo
import org.springframework.beans.factory.config.BeanDefinitionHolder
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
import org.springframework.beans.factory.xml.ParserContext
import org.w3c.dom.Attr
import org.w3c.dom.Node
import java.util.ArrayList
class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {
    override fun decorate(source: Node, holder: BeanDefinitionHolder,
                        ctx: ParserContext): BeanDefinitionHolder {
        val initializerBeanName = registerJCacheInitializer(source, ctx)
        createDependencyOnJCacheInitializer(holder, initializerBeanName)
        return holder
    }
    private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
                                                    initializerBeanName: String) {
        val definition = holder.beanDefinition as AbstractBeanDefinition
        var dependsOn = definition.dependsOn
        dependsOn = if (dependsOn == null) {
            arrayOf(initializerBeanName)
        } else {
            val dependencies = ArrayList(listOf(*dependsOn))
            dependencies.add(initializerBeanName)
            dependencies.toTypedArray()
        }
        definition.setDependsOn(*dependsOn)
    }
    private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
        val cacheName = (source as Attr).value
        val beanName = "$cacheName-initializer"
        if (!ctx.registry.containsBeanDefinition(beanName)) {
            val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
            initializer.addConstructorArg(cacheName)
            ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
        }
        return beanName
    }
}

Наконец, нам нужно зарегистрировать различные артефакты в инфраструктуре Spring XML, изменив файлы META-INF/spring.handlers и META-INF/spring.schemas следующим образом:

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd