本节介绍一些更详细的自定义 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 >
前面的配置将自定义扩展相互嵌套。 元素实际配置的类是 Component 类(如下一个示例所示)。请注意 Component 类如何不公开 Components 属性的 setter 方法。这使得通过使用 setter 注入来配置 Component 类的 bean 定义变得困难(或者说不可能)。以下清单显示了 Component 类::
复制 package com . foo ;
import java . util . ArrayList ;
import java . util . List ;
public class Component {
private String name;
private List < Component > components = new ArrayList < Component > ();
// mmm, there is no setter method for the '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;
}
}
此问题的典型解决方案是创建一个自定义FactoryBean
来公开components
属性的setter属性。以下清单显示了这样的自定义 FactoryBean
:
复制 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 ;
}
}
这很好用,但它向最终用户暴露了很多 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
:
复制 package com . foo ;
import org . springframework . beans . factory . xml . NamespaceHandlerSupport ;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
public void init () {
registerBeanDefinitionParser( "component" , new ComponentBeanDefinitionParser()) ;
}
}
接下来是自定义BeanDefinitionParser
。请记住,我们正在创建描述 ComponentFactoryBean
的 BeanDefinition
。以下清单显示了我们的自定义BeanDefinitionParser
实现:
复制 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);
}
}
最后,需要通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件将各种工件注册到 Spring XML 基础架构,如下所示:
复制 # 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
“普通”元素的自定义属性
编写您自己的自定义解析器和相关的工件并不难。但是,有时这不是正确的做法。考虑一个场景,您需要将元数据添加到已经存在的 bean 定义中。在这种情况下,您当然不想编写自己的整个自定义扩展。相反,您只想向现有的 bean 定义元素添加一个附加属性。
再举一个例子,假设您为(它不知道的)访问集群 JCache 的服务对象定义了一个 bean 定义,并且您希望确保命名的 JCache 实例在周围的集群中急切地启动。以下清单显示了这样的定义:
复制 < bean id = "checkingAccountService" class = "com.foo.DefaultCheckingAccountService"
jcache : cache-name = "checking.account" >
<!-- other dependencies here... -->
</ bean >
然后,当解析“jcache:cache-name”属性时,我们可以创建另一个 BeanDefinition
。然后这个BeanDefinition
为我们初始化命名的 JCache
。我们还可以修改“checkingAccountService”的现有 BeanDefinition
,使其依赖于这个新的 JCache
初始化 BeanDefinition
。以下清单显示了我们的` JCacheInitializer:
复制 package com . foo ;
public class JCacheInitializer {
private String name;
public JCacheInitializer ( String name) {
this . name = name;
}
public void initialize () {
// lots of JCache API calls to initialize the named cache...
}
}
现在我们可以转到自定义扩展。首先,我们需要编写描述自定义属性的 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
,如下:
复制 package com . foo ;
import org . springframework . beans . factory . xml . NamespaceHandlerSupport ;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init () {
super . registerBeanDefinitionDecoratorForAttribute ( "cache-name" ,
new JCacheInitializingBeanDefinitionDecorator() );
}
}
接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析 XML 属性,所以我们写的是 BeanDefinitionDecorator
而不是BeanDefinitionParser
。以下清单显示了我们的BeanDefinitionDecorator
实现:
复制 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;
}
}
最后,我们需要通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件将各种工件注册到 Spring XML 基础架构中,如下所示:
复制 # 在 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# 在 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd