Loading...
Spring Framework Reference Documentation 7.0.2의 XML Schema Authoring의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
2.0 버전부터 Spring은 bean을 정의하고 구성하기 위한 기본 Spring XML format에 schema 기반 extension을 추가하는 메커니즘을 제공하고 있습니다. 이 섹션에서는 자신만의 custom XML bean definition parser를 작성하고 그러한 parser를 Spring IoC container에 통합하는 방법을 다룹니다.
schema-aware XML editor를 사용하는 configuration file authoring을 용이하게 하기 위해 Spring의 확장 가능한 XML configuration 메커니즘은 XML Schema를 기반으로 합니다. 표준 Spring 배포판에 함께 제공되는 Spring의 현재 XML configuration extension에 익숙하지 않다면 먼저 XML Schemas에 대한 이전 섹션을 읽어야 합니다.
새로운 XML configuration extension을 생성하려면:
NamespaceHandler 구현을 코드로 작성합니다.BeanDefinitionParser 구현을 코드로 작성합니다
(실제 작업이 수행되는 곳입니다).통합된 예제를 위해, SimpleDateFormat 타입(java.text package의) 객체를 구성할 수 있게 해주는 XML extension(custom XML element)을 생성합니다. 작업이 끝나면 다음과 같이 SimpleDateFormat 타입의 bean definition을 정의할 수 있습니다:
1<myns:dateformat id="dateFormat" 2 pattern="yyyy-MM-dd HH:mm" 3 lenient="true"/>
(이 appendix의 뒷부분에서 훨씬 더 자세한 예제를 포함합니다. 이 첫 번째 간단한 예제의 의도는 custom extension을 만드는 기본 단계를 안내하는 것입니다.)
Spring의 IoC container에서 사용할 XML configuration extension을 생성하는 작업은 extension을 설명하는 XML Schema를 작성하는 것에서 시작합니다. 예제에서는 SimpleDateFormat 객체를 구성하기 위해 다음 schema를 사용합니다:
1<!-- myns.xsd (inside package org/springframework/samples/xml) --> 2 3<?xml version="1.0" encoding="UTF-8"?> 4<xsd:schema xmlns="http://www.mycompany.example/schema/myns" 5 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 6 xmlns:beans="http://www.springframework.org/schema/beans" 7 targetNamespace="http://www.mycompany.example/schema/myns" 8 elementFormDefault="qualified" 9 attributeFormDefault="unqualified"> 10 11 <xsd:import namespace="http://www.springframework.org/schema/beans"/> 12 13 <xsd:element name="dateformat"> 14 <xsd:complexType> 15 <xsd:complexContent> 16 <xsd:extension base="beans:identifiedType"> (1) 17 <xsd:attribute name="lenient" type="xsd:boolean"/> 18 <xsd:attribute name="pattern" type="xsd:string" use="required"/> 19 </xsd:extension> 20 </xsd:complexContent> 21 </xsd:complexType> 22 </xsd:element> 23</xsd:schema>
| 1 | 표시된 줄에는 모든 identifiable tag에 대한 extension base가 포함되어 있습니다<br>(즉, container에서 bean identifier로 사용할 수 있는 id attribute를 가진다는 의미입니다).<br>Spring이 제공하는 beans namespace를 import했기 때문에 이 attribute를 사용할 수 있습니다. |
앞의 schema를 사용하면 다음 예제에서 보듯이 <myns:dateformat/> element를 사용하여 XML application context file에서 SimpleDateFormat 객체를 직접 구성할 수 있습니다:
1<myns:dateformat id="dateFormat" 2 pattern="yyyy-MM-dd HH:mm" 3 lenient="true"/>
infrastructure class를 생성하고 나면, 앞의 XML snippet은 본질적으로 다음 XML snippet과 동일합니다:
1<bean id="dateFormat" class="java.text.SimpleDateFormat"> 2 <constructor-arg value="yyyy-MM-dd HH:mm"/> 3 <property name="lenient" value="true"/> 4</bean>
앞의 두 snippet 중 두 번째 것은 container에 bean(dateFormat이라는 이름의 SimpleDateFormat 타입)을 생성하고 몇 가지 property를 설정합니다.
configuration format을 생성하는 schema 기반 접근 방식은 schema-aware XML editor를 가진 IDE와의 긴밀한 통합을 가능하게 합니다.<br>적절하게 작성된 schema를 사용하면, 열거형에 정의된 여러 configuration option 사이에서 사용자가 선택할 수 있도록<br>자동 완성 기능을 사용할 수 있습니다.
NamespaceHandlerschema 외에도, configuration file을 parsing하는 동안 Spring이 만나는 이 특정 namespace의 모든 element를 parsing하기 위한 NamespaceHandler가 필요합니다. 이 예제에서 NamespaceHandler는 myns:dateformat element의 parsing을 처리해야 합니다.
NamespaceHandler interface에는 세 가지 method가 있습니다:
init(): NamespaceHandler의 초기화를 허용하며 handler가 사용되기 전에 Spring이 호출합니다.BeanDefinition parse(Element, ParserContext): Spring이 top-level element(bean definition 안이나 다른 namespace 안에 중첩되지 않은)를 만났을 때 호출됩니다. 이 method는 bean definition을 등록할 수도 있고, bean definition을 반환할 수도 있으며, 둘 다 할 수도 있습니다.BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): Spring이 다른 namespace의 attribute나 nested element를 만났을 때 호출됩니다. 하나 이상의 bean definition을 decoration하는 것은 (예를 들어) Spring이 지원하는 scope에서 사용됩니다. 먼저 decoration을 사용하지 않는 간단한 예제를 강조한 다음, 좀 더 고급 예제에서 decoration을 보여줍니다.전체 namespace에 대해 자체 NamespaceHandler를 작성할 수도 있고 (따라서 namespace의 모든 element를 parsing하는 코드를 제공할 수도 있지만), Spring XML configuration file의 각 top-level XML element가 단일 bean definition으로 귀결되는 경우가 종종 있습니다(우리의 경우처럼, 단일 <myns:dateformat/> element가 단일 SimpleDateFormat bean definition으로 귀결됩니다). Spring은 이 시나리오를 지원하는 여러 편의 class를 제공합니다. 다음 예제에서는 NamespaceHandlerSupport class를 사용합니다:
1package org.springframework.samples.xml; 2 3import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 5public class MyNamespaceHandler extends NamespaceHandlerSupport { 6 7 public void init() { 8 registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser()); 9 } 10}
1package org.springframework.samples.xml 2 3import org.springframework.beans.factory.xml.NamespaceHandlerSupport 4 5class MyNamespaceHandler : NamespaceHandlerSupport() { 6 7 override fun init() { 8 registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser()) 9 } 10}
이 class에는 실제로 많은 parsing logic이 없다는 것을 알 수 있습니다. 실제로 NamespaceHandlerSupport class에는 delegation에 대한 내장 개념이 있습니다. 이 class는 element를 parsing해야 할 때 자신의 namespace 안의 element를 parsing하기 위해 위임할 수 있는 여러 개의 BeanDefinitionParser instance 등록을 지원합니다.
이러한 명확한 관심사의 분리는 NamespaceHandler가 자신의 namespace에 있는 모든 custom element의 parsing orchestration을 처리하면서 XML parsing의 힘든 작업을 BeanDefinitionParser에 위임할 수 있게 해줍니다. 이는 각 BeanDefinitionParser가 다음 단계에서 볼 수 있듯이 단일 custom element를 parsing하는 logic만을 포함한다는 것을 의미합니다.
BeanDefinitionParserBeanDefinitionParser는 NamespaceHandler가 특정 bean definition parser에 mapping된 타입(dateformat인 이 경우)의 XML element를 만났을 때 사용됩니다. 다시 말해, BeanDefinitionParser는 schema에 정의된 하나의 구별되는 top-level XML element를 parsing하는 역할을 합니다.
parser에서는 XML element(그리고 그 subelement에도)에 접근할 수 있으므로 다음 예제에서 볼 수 있듯이 custom XML 내용을 parsing할 수 있습니다:
1package org.springframework.samples.xml; 2 3import org.springframework.beans.factory.support.BeanDefinitionBuilder; 4import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; 5import org.springframework.util.StringUtils; 6import org.w3c.dom.Element; 7 8import java.text.SimpleDateFormat; 9 10public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { (1) 11 12 protected Class getBeanClass(Element element) { 13 return SimpleDateFormat.class; (2) 14 } 15 16 protected void doParse(Element element, BeanDefinitionBuilder bean) { 17 // this will never be null since the schema explicitly requires that a value be supplied 18 String pattern = element.getAttribute("pattern"); 19 bean.addConstructorArgValue(pattern); 20 21 // this however is an optional property 22 String lenient = element.getAttribute("lenient"); 23 if (StringUtils.hasText(lenient)) { 24 bean.addPropertyValue("lenient", Boolean.valueOf(lenient)); 25 } 26 } 27 28}
| 1 | 단일 BeanDefinition을 생성하는 많은 기본적인 힘든 작업을 처리하기 위해 Spring이 제공하는<br>AbstractSingleBeanDefinitionParser를 사용합니다. |
| 2 | 단일 BeanDefinition이 나타내는 타입을 AbstractSingleBeanDefinitionParser superclass에 제공합니다. |
1package org.springframework.samples.xml 2 3import org.springframework.beans.factory.support.BeanDefinitionBuilder 4import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser 5import org.springframework.util.StringUtils 6import org.w3c.dom.Element 7 8import java.text.SimpleDateFormat 9 10class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { (1) 11 12 override fun getBeanClass(element: Element): Class<*>? { (2) 13 return SimpleDateFormat::class.java 14 } 15 16 override fun doParse(element: Element, bean: BeanDefinitionBuilder) { 17 // this will never be null since the schema explicitly requires that a value be supplied 18 val pattern = element.getAttribute("pattern") 19 bean.addConstructorArgValue(pattern) 20 21 // this however is an optional property 22 val lenient = element.getAttribute("lenient") 23 if (StringUtils.hasText(lenient)) { 24 bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient)) 25 } 26 } 27}
| 1 | 단일 BeanDefinition을 생성하는 많은 기본적인 힘든 작업을 처리하기 위해 Spring이 제공하는<br>AbstractSingleBeanDefinitionParser를 사용합니다. |
| 2 | 단일 BeanDefinition이 나타내는 타입을 AbstractSingleBeanDefinitionParser superclass에 제공합니다. |
이 간단한 경우에는 이것이 우리가 해야 할 전부입니다. 단일 BeanDefinition의 생성은 AbstractSingleBeanDefinitionParser superclass에 의해 처리되며, bean definition의 고유 identifier의 추출 및 설정도 마찬가지입니다.
코딩은 끝났습니다. 남은 작업은 Spring XML parsing infrastructure가 우리의 custom element를 인식하도록 만드는 것입니다. 이를 위해 custom namespaceHandler와 custom XSD file을 두 개의 특수 목적 properties file에 등록합니다.
이 properties file은 모두 application의 META-INF directory에 위치하며 예를 들어 JAR file의 binary class와 함께 배포될 수 있습니다. Spring XML parsing infrastructure는 이 특수 properties file을 사용하여 새 extension을 자동으로 인식하며, 그 format은 다음 두 섹션에서 자세히 설명합니다.
META-INF/spring.handlersspring.handlers라는 properties file에는 XML Schema URI와 namespace handler class 간의 mapping이 포함됩니다. 예제에서는 다음을 작성해야 합니다:
1http://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(: 문자는 Java properties format에서 유효한 구분자이므로 URI의 : 문자는 역슬래시로 escape해야 합니다.)
key-value pair의 첫 번째 부분(key)은 custom namespace extension과 연관된 URI이며 custom XSD schema에 지정된 targetNamespace attribute의 값과 정확히 일치해야 합니다.
META-INF/spring.schemasspring.schemas라는 properties file에는 XML Schema location과 (XML file에서 schema 선언과 함께 xsi:schemaLocation attribute의 일부로 schema를 사용하는 schema location)이 classpath resource에 mapping된 값이 포함됩니다.
이 file은 Spring이 schema file을 검색하기 위해 Internet access가 필요한 기본 EntityResolver를 절대 사용하지 않도록 하기 위해 필요합니다. 이 properties file에 mapping을 지정하면 Spring은 classpath에서 schema(이 경우 org.springframework.samples.xml package의 myns.xsd)를 검색합니다. 다음 snippet은 custom schema에 대해 추가해야 할 줄을 보여 줍니다:
1http://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(: 문자를 escape해야 함을 기억하십시오.)
XSD file(또는 여러 개의 file)을 classpath의 NamespaceHandler 및 BeanDefinitionParser class 바로 옆에 배포하는 것이 좋습니다.
직접 구현한 custom extension을 사용하는 것은 Spring이 제공하는 “custom” extension을 사용하는 것과 다르지 않습니다. 다음 예제는 이전 단계에서 개발한 custom <dateformat/> element를 Spring XML configuration file에서 사용하는 방법을 보여 줍니다:
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:myns="http://www.mycompany.example/schema/myns" 5 xsi:schemaLocation=" 6 http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd"> 8 9 <!-- as a top-level bean --> 10 <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> (1) 11 12 <bean id="jobDetailTemplate" abstract="true"> 13 <property name="dateFormat"> 14 <!-- as an inner bean --> 15 <myns:dateformat pattern="HH:mm MM-dd-yyyy"/> 16 </property> 17 </bean> 18 19</beans>
| 1 | custom bean입니다. |
이 섹션에서는 custom XML extension의 좀 더 자세한 예제를 제시합니다.
이 섹션에서 제시하는 예제는 다음 configuration의 target을 만족시키기 위해 필요한 다양한 artifact를 작성하는 방법을 보여 줍니다:
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:foo="http://www.foo.example/schema/component" 5 xsi:schemaLocation=" 6 http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd"> 8 9 <foo:component id="bionic-family" name="Bionic-1"> 10 <foo:component name="Mother-1"> 11 <foo:component name="Karate-1"/> 12 <foo:component name="Sport-1"/> 13 </foo:component> 14 <foo:component name="Rock-1"/> 15 </foo:component> 16 17</beans>
앞의 configuration은 custom extension을 서로 안에 중첩합니다. 실제로 <foo:component/> element에 의해 구성되는 class는 Component class입니다(다음 예제에 표시됨).
Component class가 components property에 대한 setter method를 노출하지 않는다는 점에 주목하십시오. 이는 setter injection을 사용하여 Component class에 대한 bean definition을 구성하는 것을 어렵게 (또는 사실상 불가능하게) 만듭니다. 다음 listing은 Component class를 보여 줍니다:
1package com.foo; 2 3import java.util.ArrayList; 4import java.util.List; 5 6public class Component { 7 8 private String name; 9 private List<Component> components = new ArrayList<Component> (); 10 11 // there is no setter method for the 'components' 12 public void addComponent(Component component) { 13 this.components.add(component); 14 } 15 16 public List<Component> getComponents() { 17 return components; 18 } 19 20 public String getName() { 21 return name; 22 } 23 24 public void setName(String name) { 25 this.name = name; 26 } 27}
1package com.foo 2 3import java.util.ArrayList 4 5class Component { 6 7 var name: String? = null 8 private val components = ArrayList<Component>() 9 10 // there is no setter method for the 'components' 11 fun addComponent(component: Component) { 12 this.components.add(component) 13 } 14 15 fun getComponents(): List<Component> { 16 return components 17 } 18}
이 문제에 대한 일반적인 해결책은 components property에 대한 setter property를 노출하는 custom FactoryBean을 만드는 것입니다. 다음 listing은 이러한 custom FactoryBean을 보여 줍니다:
1package com.foo; 2 3import org.springframework.beans.factory.FactoryBean; 4 5import java.util.List; 6 7public class ComponentFactoryBean implements FactoryBean<Component> { 8 9 private Component parent; 10 private List<Component> children; 11 12 public void setParent(Component parent) { 13 this.parent = parent; 14 } 15 16 public void setChildren(List<Component> children) { 17 this.children = children; 18 } 19 20 public Component getObject() throws Exception { 21 if (this.children != null && this.children.size() > 0) { 22 for (Component child : children) { 23 this.parent.addComponent(child); 24 } 25 } 26 return this.parent; 27 } 28 29 public Class<Component> getObjectType() { 30 return Component.class; 31 } 32 33 public boolean isSingleton() { 34 return true; 35 } 36}
1package com.foo 2 3import org.springframework.beans.factory.FactoryBean 4import org.springframework.stereotype.Component 5 6class ComponentFactoryBean : FactoryBean<Component> { 7 8 private var parent: Component? = null 9 private var children: List<Component>? = null 10 11 fun setParent(parent: Component) { 12 this.parent = parent 13 } 14 15 fun setChildren(children: List<Component>) { 16 this.children = children 17 } 18 19 override fun getObject(): Component? { 20 if (this.children != null && this.children!!.isNotEmpty()) { 21 for (child in children!!) { 22 this.parent!!.addComponent(child) 23 } 24 } 25 return this.parent 26 } 27 28 override fun getObjectType(): Class<Component>? { 29 return Component::class.java 30 } 31 32 override fun isSingleton(): Boolean { 33 return true 34 } 35}
이는 잘 동작하지만, 많은 Spring plumbing을 end user에게 노출합니다. 여기서 할 일은 이러한 Spring plumbing을 모두 숨기는 custom extension을 작성하는 것입니다.
앞에서 설명한 단계를 따르면, 먼저 다음 listing과 같이 custom tag의 구조를 정의하는 XSD schema를 생성하는 것부터 시작합니다:
1<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 3<xsd:schema xmlns="http://www.foo.example/schema/component" 4 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 5 targetNamespace="http://www.foo.example/schema/component" 6 elementFormDefault="qualified" 7 attributeFormDefault="unqualified"> 8 9 <xsd:element name="component"> 10 <xsd:complexType> 11 <xsd:choice minOccurs="0" maxOccurs="unbounded"> 12 <xsd:element ref="component"/> 13 </xsd:choice> 14 <xsd:attribute name="id" type="xsd:ID"/> 15 <xsd:attribute name="name" use="required" type="xsd:string"/> 16 </xsd:complexType> 17 </xsd:element> 18 19</xsd:schema>
다시 앞에서 설명한 절차를 따라 custom NamespaceHandler를 생성합니다:
1package com.foo; 2 3import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 5public class ComponentNamespaceHandler extends NamespaceHandlerSupport { 6 7 public void init() { 8 registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser()); 9 } 10}
1package com.foo 2 3import org.springframework.beans.factory.xml.NamespaceHandlerSupport 4 5class ComponentNamespaceHandler : NamespaceHandlerSupport() { 6 7 override fun init() { 8 registerBeanDefinitionParser("component", ComponentBeanDefinitionParser()) 9 } 10}
다음은 custom BeanDefinitionParser입니다. ComponentFactoryBean을 설명하는 BeanDefinition을 생성하고 있다는 점을 기억하십시오. 다음 listing은 custom BeanDefinitionParser 구현을 보여 줍니다:
1package com.foo; 2 3import org.springframework.beans.factory.config.BeanDefinition; 4import org.springframework.beans.factory.support.AbstractBeanDefinition; 5import org.springframework.beans.factory.support.BeanDefinitionBuilder; 6import org.springframework.beans.factory.support.ManagedList; 7import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; 8import org.springframework.beans.factory.xml.ParserContext; 9import org.springframework.util.xml.DomUtils; 10import org.w3c.dom.Element; 11 12import java.util.List; 13 14public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser { 15 16 protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { 17 return parseComponentElement(element); 18 } 19 20 private static AbstractBeanDefinition parseComponentElement(Element element) { 21 BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class); 22 factory.addPropertyValue("parent", parseComponent(element)); 23 24 List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component"); 25 if (childElements != null && childElements.size() > 0) { 26 parseChildComponents(childElements, factory); 27 } 28 29 return factory.getBeanDefinition(); 30 } 31 32 private static BeanDefinition parseComponent(Element element) { 33 BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class); 34 component.addPropertyValue("name", element.getAttribute("name")); 35 return component.getBeanDefinition(); 36 } 37 38 private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) { 39 ManagedList<BeanDefinition> children = new ManagedList<>(childElements.size()); 40 for (Element element : childElements) { 41 children.add(parseComponentElement(element)); 42 } 43 factory.addPropertyValue("children", children); 44 } 45}
1package com.foo 2 3import org.springframework.beans.factory.config.BeanDefinition 4import org.springframework.beans.factory.support.AbstractBeanDefinition 5import org.springframework.beans.factory.support.BeanDefinitionBuilder 6import org.springframework.beans.factory.support.ManagedList 7import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser 8import org.springframework.beans.factory.xml.ParserContext 9import org.springframework.util.xml.DomUtils 10import org.w3c.dom.Element 11 12class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() { 13 14 override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? { 15 return parseComponentElement(element) 16 } 17 18 private fun parseComponentElement(element: Element): AbstractBeanDefinition { 19 val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java) 20 factory.addPropertyValue("parent", parseComponent(element)) 21 22 val childElements = DomUtils.getChildElementsByTagName(element, "component") 23 if (childElements != null && childElements.size > 0) { 24 parseChildComponents(childElements, factory) 25 } 26 27 return factory.getBeanDefinition() 28 } 29 30 private fun parseComponent(element: Element): BeanDefinition { 31 val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java) 32 component.addPropertyValue("name", element.getAttribute("name")) 33 return component.beanDefinition 34 } 35 36 private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) { 37 val children = ManagedList<BeanDefinition>(childElements.size) 38 for (element in childElements) { 39 children.add(parseComponentElement(element)) 40 } 41 factory.addPropertyValue("children", children) 42 } 43}
마지막으로, 다양한 artifact를 Spring XML infrastructure에 등록하기 위해 META-INF/spring.handlers 및 META-INF/spring.schemas file을 다음과 같이 수정해야 합니다:
1# in 'META-INF/spring.handlers' 2http://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
1# in 'META-INF/spring.schemas' 2http://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
자신만의 custom parser와 관련 artifact를 작성하는 것은 어렵지 않습니다. 그러나 항상 올바른 일인 것은 아닙니다. 이미 존재하는 bean definition에 metadata를 추가해야 하는 시나리오를 생각해 보십시오.
이 경우 전체 custom extension을 직접 작성하고 싶지는 않을 것입니다. 대신, 기존 bean definition element에 추가 attribute만 추가하고자 할 것입니다.
또 다른 예로, service object에 대한 bean definition을 정의한다고 가정해 보겠습니다. 이 service object는 (자신은 알지 못하지만) clustered JCache에 접근하며, 주변 cluster 내에서 named JCache instance가 eager하게 시작되도록 보장하고자 합니다. 다음 listing은 이러한 정의를 보여 줍니다:
1<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService" 2 jcache:cache-name="checking.account"> 3 <!-- other dependencies here... --> 4</bean>
그런 다음 'jcache:cache-name' attribute가 parsing될 때 또 다른 BeanDefinition을 생성할 수 있습니다. 이 BeanDefinition은 named JCache를 초기화합니다. 또한 기존의 'checkingAccountService'에 대한 BeanDefinition을 수정하여 이 새로운 JCache-initializing BeanDefinition에 대한 dependency를 갖도록 할 수 있습니다. 다음 listing은 JCacheInitializer를 보여 줍니다:
1package com.foo; 2 3public class JCacheInitializer { 4 5 private final String name; 6 7 public JCacheInitializer(String name) { 8 this.name = name; 9 } 10 11 public void initialize() { 12 // lots of JCache API calls to initialize the named cache... 13 } 14}
1package com.foo 2 3class JCacheInitializer(private val name: String) { 4 5 fun initialize() { 6 // lots of JCache API calls to initialize the named cache... 7 } 8}
이제 custom extension으로 넘어갈 수 있습니다. 먼저, 다음과 같이 custom attribute를 설명하는 XSD schema를 작성해야 합니다:
1<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 3<xsd:schema xmlns="http://www.foo.example/schema/jcache" 4 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 5 targetNamespace="http://www.foo.example/schema/jcache" 6 elementFormDefault="qualified"> 7 8 <xsd:attribute name="cache-name" type="xsd:string"/> 9 10</xsd:schema>
다음으로, 다음과 같이 관련 NamespaceHandler를 생성해야 합니다:
1package com.foo; 2 3import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 5public class JCacheNamespaceHandler extends NamespaceHandlerSupport { 6 7 public void init() { 8 super.registerBeanDefinitionDecoratorForAttribute("cache-name", 9 new JCacheInitializingBeanDefinitionDecorator()); 10 } 11 12}
1package com.foo 2 3import org.springframework.beans.factory.xml.NamespaceHandlerSupport 4 5class JCacheNamespaceHandler : NamespaceHandlerSupport() { 6 7 override fun init() { 8 super.registerBeanDefinitionDecoratorForAttribute("cache-name", 9 JCacheInitializingBeanDefinitionDecorator()) 10 } 11 12}
다음으로, parser를 생성해야 합니다. 이 경우 XML attribute를 parsing할 것이므로 BeanDefinitionParser가 아니라 BeanDefinitionDecorator를 작성합니다. 다음 listing은 BeanDefinitionDecorator 구현을 보여 줍니다:
1package com.foo; 2 3import org.springframework.beans.factory.config.BeanDefinitionHolder; 4import org.springframework.beans.factory.support.AbstractBeanDefinition; 5import org.springframework.beans.factory.support.BeanDefinitionBuilder; 6import org.springframework.beans.factory.xml.BeanDefinitionDecorator; 7import org.springframework.beans.factory.xml.ParserContext; 8import org.w3c.dom.Attr; 9import org.w3c.dom.Node; 10 11import java.util.ArrayList; 12import java.util.Arrays; 13import java.util.List; 14 15public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator { 16 17 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 18 19 public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder, 20 ParserContext ctx) { 21 String initializerBeanName = registerJCacheInitializer(source, ctx); 22 createDependencyOnJCacheInitializer(holder, initializerBeanName); 23 return holder; 24 } 25 26 private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder, 27 String initializerBeanName) { 28 AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition()); 29 String[] dependsOn = definition.getDependsOn(); 30 if (dependsOn == null) { 31 dependsOn = new String[]{initializerBeanName}; 32 } else { 33 List dependencies = new ArrayList(Arrays.asList(dependsOn)); 34 dependencies.add(initializerBeanName); 35 dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY); 36 } 37 definition.setDependsOn(dependsOn); 38 } 39 40 private String registerJCacheInitializer(Node source, ParserContext ctx) { 41 String cacheName = ((Attr) source).getValue(); 42 String beanName = cacheName + "-initializer"; 43 if (!ctx.getRegistry().containsBeanDefinition(beanName)) { 44 BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class); 45 initializer.addConstructorArg(cacheName); 46 ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition()); 47 } 48 return beanName; 49 } 50}
1package com.foo 2 3import org.springframework.beans.factory.config.BeanDefinitionHolder 4import org.springframework.beans.factory.support.AbstractBeanDefinition 5import org.springframework.beans.factory.support.BeanDefinitionBuilder 6import org.springframework.beans.factory.xml.BeanDefinitionDecorator 7import org.springframework.beans.factory.xml.ParserContext 8import org.w3c.dom.Attr 9import org.w3c.dom.Node 10 11import java.util.ArrayList 12 13class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator { 14 15 override fun decorate(source: Node, holder: BeanDefinitionHolder, 16 ctx: ParserContext): BeanDefinitionHolder { 17 val initializerBeanName = registerJCacheInitializer(source, ctx) 18 createDependencyOnJCacheInitializer(holder, initializerBeanName) 19 return holder 20 } 21 22 private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder, 23 initializerBeanName: String) { 24 val definition = holder.beanDefinition as AbstractBeanDefinition 25 var dependsOn = definition.dependsOn 26 dependsOn = if (dependsOn == null) { 27 arrayOf(initializerBeanName) 28 } else { 29 val dependencies = ArrayList(listOf(*dependsOn)) 30 dependencies.add(initializerBeanName) 31 dependencies.toTypedArray() 32 } 33 definition.setDependsOn(*dependsOn) 34 } 35 36 private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String { 37 val cacheName = (source as Attr).value 38 val beanName = "$cacheName-initializer" 39 if (!ctx.registry.containsBeanDefinition(beanName)) { 40 val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java) 41 initializer.addConstructorArg(cacheName) 42 ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition()) 43 } 44 return beanName 45 } 46}
마지막으로, 다양한 artifact를 Spring XML infrastructure에 등록하기 위해 META-INF/spring.handlers 및 META-INF/spring.schemas file을 다음과 같이 수정해야 합니다:
1# in 'META-INF/spring.handlers' 2http://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
1# in 'META-INF/spring.schemas' 2http://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
XML Schemas
Application Startup Steps