概述
Spring中,从AbstractXmlApplicationContext开始,通过对NamespaceHandler & BeanDefinitionParser,来实现自定义xml配置的功能。
1 xml加载解析
xml文件的加载,从AbstractXmlApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)中开始实现:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
// 加载bean定义
loadBeanDefinitions(beanDefinitionReader);
}
加载过程是由XmlBeanDefinitionReader实现的,XmlBeanDefinitionReader继承了AbstractBeanDefinitionReader。
Spring环境的加载,需要调用AbstractApplicationContext.refresh(),从refresh()开始,完整过程如下:
-
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
-
- AbstractRefreshableApplicationContext.refreshBeanFactory();
-
- AbstractXmlApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory);
-
- AbstractBeanDefinitionReader.loadBeanDefinitions(Resource... resources);
-
- XmlBeanDefinitionReader.loadBeanDefinitions(Resource);
-
- XmlBeanDefinitionReader.doLoadBeanDefinitions;
-
- XmlBeanDefinitionReader.registerBeanDefinitions;
-
- DefaultBeanDefinitionDocumentReader.registerBeanDefinitions;
-
- DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions;
-
- DefaultBeanDefinitionDocumentReader.parseBeanDefinitions。 DefaultBeanDefinitionDocumentReader.parseBeanDefinitions实现如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 如果是默认的namespace,则执行parseDefaultElement加载默认的xml元素,如import, alias, bean等;
parseDefaultElement(ele, delegate);
}
else {
// 否则执行BeanDefinitionParserDelegate.parseCustomElement(root)加载自定义的xml元素。
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
BeanDefinitionParserDelegate.parseCustomElement中获取了自定义的namespace,并根据namespace获取NamespaceHandler,然后执行NamespaceHandler.parse,并返回BeanDefinition.
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 获取namespace
String namespaceUri = getNamespaceURI(ele);
// 获取对应的NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 返回BeanDefinition
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
具体过程如下:
-
1.this.readerContext.getNamespaceHandlerResolver() :
-
- 返回 NamespaceHandlerResolver,实现类是DefaultNamespaceHandlerResolver;
-
- DefaultNamespaceHandlerResolver中定义了 String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
-
-
2.DefaultNamespaceHandlerResolver.resolve :
-
- 执行了NamespaceHandler.init
-
- 根据spring.handlers的内容和当前的namespace, 并返回了自定义的NamespaceHandler的实现。
-
2 NamespaceHander & BeanDefinitionParser
- org.springframework.beans.factory.xml.BeanDefinitionParser:解析xml并返回BeanDefinition对象。
- org.springframework.beans.factory.xml.NamespaceHander:将实现的BeanDefinitionParser对象注册到指定xml元素。
所以,一般直接继承NamespaceHandlerSupport 或 AbstractSingleBeanDefinitionParser即可。
3 完整实现步骤
3.1 定义一个Bean
public class ServerConfig {
private String host;
private int port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
3.2 创建xsd文件
创建xsd,并放到META-INF下;如文件名为custom.xsd。
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
xmlns="custom"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="custom"
>
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="serverConfig">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="port" type="xsd:int" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
3.3 实现BeanDefinitionParser接口
继承AbstractSingleBeanDefinitionParser,并重写getBeanClass和doParse两个方法,解析custom.xsd中定义的xml节点的属性。
public class ServerConfigBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return ServerConfig.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("host");
String port = element.getAttribute("port");
if (StringUtils.hasText(name)) {
builder.addPropertyValue("host", name);
}
if (StringUtils.hasText(port)) {
builder.addPropertyValue("port", Integer.valueOf(port));
}
}
}
3.4 实现NameSpaceHander接口
继承NamespaceHandlerSupport,重写init()方法:将custom.xsd中定义的xml根节点,注册为上面实现的ServerConfigBeanDefinitionParser对象。
public class ServerConfigNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("serverConfig", new ServerConfigBeanDefinitionParser());
}
}
3.5 指定xsd文件
classpath下,新建文件 META-INF/spring.schemas,并写入以下内容:
custom.xsd=classpath:META-INF/custom.xsd
3.6 指定NameSpaceHander的实现类
classpath下,新建文件 META-INF/spring.hander,并写入以下内容:
custom=xxx.xxx.ServerConfigNamespaceHandler
3.7 spring中引入xsd
在spring的xml配置中,引入上面声明的xsd:
<?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:tinyrpc="custom"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
custom
META-INF/custom.xsd"
default-lazy-init="false" default-autowire="byName">
<custom:serverConfig id=”testServer" host=”localhost" port=”8888"></custom:serverConfig>
</beans>
即可声明一个id为testServer的Bean。
4 总结
综上所述,实现基本的自定义xml,按照如下几个步骤即可:
- 1.定义Bean;
- 2.创建xsd;
- 3.继承AbstractSingleBeanDefinitionParser并重写相关方法;
- 4.继承NamespaceHandlerSupport并重写相关方法;
- 5.通过META-INF/spring.schemas指定xsd文件;
- 6.通过META-INF/spring.hander指定NamespaceHander;
- 7 在spring的xml配置中,引入声明的xsd。
Spring中的aop配置,事务配置等,阿里Dubbo,美团的Pigeon 中自定义的xml配置,均由此方式实现。
了解spring的加载过程,可参考Spring加载过程及核心类。