从XML配置文件到Document的转换完成后,就开始解析各种元素了,解析需要区分默认标签和自定义标签。本文介绍自定义标签的解析。
解析过程和用法息息相关,如果不了解自定义标签的使用,那么解析过程中的一些步骤就会疑惑。所以,先介绍如何使用自定义标签。
使用自定义标签
1.创建一个需要扩展的组件。如下创建了一个普通的POJO,用来接收配置参数。
public class User {
private String userName;
private String email;
// setXxx、getXxx
}
2.定义一个XSD文件描述组件
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.lexueba.com/schema/user" xmlns:tns="http://www.lexueba.com/schema/user" elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="userName" type="string"/>
<attribute name="email" type="string"/>
</complexType>
</element>
</schema>
这是一个Schema文件,它的作用是定义XML文件的合法结构,是DTD的替代者。
xmlns="http://www.w3.org/2001/XMLSchema"
表示 schema 中用到的元素和数据类型来自命名空间”http://www.w3.org/2001/XMLSchema”
elementFormDefault="qualified"
指出任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定。
//targetNameSpace?
3.实现BeanDefinitionParser接口,定义如何解析XSD文件。
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
// Element对应的类
protected Class getBeanClass(Element element){
return User.class;
}
//从element中提取并解析对应的元素
protected void doParse(Element element, BeanDefinitionBuilder bean){
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
if(StringUtils.hasText(userName)){
bean.addPropertyValue("userName", userName);
}
if(StringUtils.hasText(email)){
bean.addPropertyValue("emai", email);
}
}
}
4.扩展NamespaceHandlerSupport,目的是将组件注册到Spring容器。
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
// 意味着当遇到自定义标签<user:aaa>这样类似于以user开头的元素,
// 就会把这个元素扔给对应的UserBeanDefinitionParser去解析
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
5.修改Spring.handlers
http\:www.lexueba.com/schema/user=com.tony.MyNamespaceHandler
6.修改Spring.schemas
http\://www.lexueba.com/schema/user.xsd=META-INF/Spring-test.xsd
自定义标签解析
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
这段代码的思路很清晰:
1.根据对应的bean获取对应的命名空间;
2.根据命名空间解析的对应的处理器;
3.根据用户自定义的处理器进行解析;
下面展开阐述。
获取命名空间
org.w3c.dom.Node中提供了获取命名空间的方法:
public String getNamespaceURI(Node node){
return node.getNamespaceURI();
}
提取自定义标签处理器
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
}
1.getHandlerMappings()
使用自定义标签,会在/META-INF/文件夹下Spring.handlers中设置命名空间和命名空间处理器的映射关系,比如:
http\://www.abc.com/schema/user=com.tony.MyNamespaceHandler
getHandlerMappings()方法读取Spring.handlers,将这种映射关系缓存在ConcurrentHashMap中。
2.DefaultNamespaceHandlerResolver
1)根据命名空间从缓存map中尝试获取;
2)如果可以获取到,且没有做过解析,得到的是类路径,使用反射将类路径转化为类;
3) 根据class初始化实例,调用init()方法进行BeanDefinitionParser的注册;
4)将实例化好的NamespaceHandler放置到handlerMappings中,返回该实例;
标签解析
父类NamespaceHandlerSupprt的parse方法:
public BeanDefinition parse(Element element, ParserContext parserContext){
return findParserForElement(element,parserContext).parse(element,parserContext);
}
1.寻找解析器
在自定义NamespaceHandler执行init()方法时,已经将自定义标签名和解析器注册到parsers中了,所以这里先获取自定义标签名,然后到parsers中找就行;
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//获取元素名称,也就是<myname:user>中的user
String localName = parserContext.getDelegate().getLocalName(element);
// 根据user找到对应的解析器,就是在registerBeanDefinitionParser("user",new UserBeanDefinitionParser())注册的解析器
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
2.parseInternal
AbstractBeanDefinition definition = parseInternal(element, parserContext);
真正的解析落在这一句上,parseInternal方法中,在经过对beanClass、scope、lazyInit等属性准备后,最终将解析工作委托给我们自定义的MyNamespaceHandler的doParse方法上。