扩展spring的xml

spring框架在java世界应用广泛,IOC、AOP,包括最近的spring boot等,体系庞大。平时spring应用很少,也没特别的经验要说。但在适配自己的框架和spring方面,也摸索了几天,有了一点心得。所有的适配都是基于spring框架对外开放的钩子…今天要说的比较简单,是关于xml的

开发参考了spring doc

xml extension

这部分可能是spring最简单和最常见的扩展了,基本包含了4个步骤:

  • Authoring an XML schema to describe your custom element(s).
  • Coding a custom NamespaceHandler implementation (this is an easy step, don’t worry).
  • Coding one or more BeanDefinitionParser implementations (this is where the real work is done).
  • Registering the above artifacts with Spring (this too is an easy step).

1.xml扩展首先要做的是定义xsd,这个可以参考语法。你必须先了解xsd schema的规范,当然要玩的更好,还可以多学习下spring xsd的定义。spring的扩展是通过在META-INF下的2个文件起作用:spring.handlers和spring.schemas。

2.下一步就是注册解析器,在NamespaceHandler的init方法中registerBeanDefinitionParser("common", new DefinitionParser());

3.BeanDefinitionParser的实现是真正需要下功夫的地方,不过spring也给我们提供了基本的实现AbstractSingleBeanDefinitionParser–>AbstractBeanDefinitionParser–>BeanDefinitionParser,这3个是我经常用到的。

4.当我们需要处理element下带有子节点时,通常的做法是增加一个FactoryBean,这个类是和beanFactory相关联的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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);
}
}

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;
}
}

这2段代码中还是很好理解的,DomUtils.getChildElementsByTagName(element, "component")能够获得子节点,然后再按照老方法去解析。

5.另一个问题是id的问题,我们虽然在xsd中没有定义id属性,但是当通过BeanFactory.getBean时,依然会提示我们top-level必须有id属性,这是为什么呢?显然是因为spring默认bean是要有id来区分的。但是我们又不愿意在自己的节点上加这个属性,解决办法是auto generate id。在AbstractBeanDefinitionParser中有一个protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException方法,只要重写就可以完成,但是必须要小心,这个id是不能出现重复的。

如果做到这些,基本就完成了spring xml的扩展,并不算难。