自定义标签解析
1 示例
自定义标签的实现:
- XSD文件、META-INF/spring.schemas文件
- 自定义标签的解析类:BeanDefinitionParser
- 注册自定义标签解析类:NamespaceHandlerSupport、META-INF/spring.handlers
1.1 自定义标签
1. POJO
public class UserPO {
private String userName;
private String email;
// setter,getter
}
2. 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:myname="http://www.lexueba.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user.xsd">
<myname:user id="userPO" email="test@163.com" userName="zxt" />
</beans>
1.2 自定义XSD文件
1. XSD文件
该文件位置:META-INF/customer_user.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="id" type="string" />
<attribute name="userName" type="string" />
<attribute name="email" type="string" />
</complexType>
</element>
</schema>
2. spring.schemas文件
http\://www.lexueba.com/schema/user.xsd=META-INF/custom_user.xsd
1.3 解析自定义标签
实现BeanDefinitionParsr接口用于解析自定义标签。这里推荐使用AbstractSingleBeanDefinitionParser
类,内容如下:
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
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("email", email);
}
}
@Override
protected Class getBeanClass(Element element) {
return UserPO.class;
}
}
1.4 注册解析类
1. spring.handlers
该文件用于根据XML文件中自定义标签的命名空间URI找到对应的
http\://www.lexueba.com/schema/user=wolfdriver.learn.spring.bean.customXml.UserNamespaceHandler
2. NamespaceHandlerSupport
推荐继承NamespaceHandlerSupport
抽象类完成。代码如下:
public class UserNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
2. 源码分析
2.1 EntityResolver用法
在Spring中使用SAX读取XML文件。在加载、解析XML文件的过程中SAX会验证XML文档的规范性(DTD或XSD模式)。对于DTD或XSD文件的读取,默认寻找规则是网络下载,利用EntityResolver
接口我们可以自定义XML文档规范声明的寻找方式。改接口定义如下:
package org.xml.sax;
import java.io.IOException;
/**
* Basic interface for resolving entities.
* <pre>
* public class MyResolver implements EntityResolver {
* public InputSource resolveEntity (String publicId, String systemId) {
* if (systemId.equals("http://www.myhost.com/today")) {
* // return a special input source
* MyReader reader = new MyReader();
* return new InputSource(reader);
* } else {
* // use the default behaviour
* return null;
* }
* }
* }
* </pre>
* @since SAX 1.0
* @see org.xml.sax.XMLReader#setEntityResolver
* @see org.xml.sax.InputSource
*/
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
}
注意:
该接口入参:publicId和systemId。
- 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:myname="http://www.lexueba.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
publicId: null
systemId: http://www.springframework.org/schema/beans
- DTD文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTO BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
publicId: -//Spring//DTO BEAN 2.0//EN-//Spring//DTO BEAN 2.0//EN
systemId: http://www.Springframework.org/dtd/Spring-beans-2.0.dtd
2.2 Spring利用EntityResolver寻找XSD并校验
XmlBeanDefinitionReader
类中通过getEntityResolver()
方法获取EntityResolverEntityResolver
接口的默认实现类DelegatingEntityResolver
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
DelegatingEntityResolver
类以委托的形式,内部分别封装了dtd和xsd文件的EntityResolver
的实习类,如下:
public class DelegatingEntityResolver implements EntityResolver {
public static final String DTD_SUFFIX = ".dtd";
public static final String XSD_SUFFIX = ".xsd";
private final EntityResolver dtdResolver;
private final EntityResolver schemaResolver;
public DelegatingEntityResolver(ClassLoader classLoader) {
this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
// 省略其他构造器
/*根据systemId判断具体使用哪个EntityResolver解析器进行查找。*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
由上述代码可知,XSD使用PluggableSchemaResolver
进行XSD文件的查找。具体代码如下:
public InputSource resolveEntity(String publicId, String systemId) throws IOException {
if (systemId != null) {
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
} catch (FileNotFoundException ex) {/*省略日志*/}
}
}
return null;
}
/* schemaMappingsLocation:META-INF/spring.schemas*/
private Map<String, String> getSchemaMappings() {
if (this.schemaMappings == null) {
synchronized (this) {
if (this.schemaMappings == null) {
try {
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
} catch (IOException ex) { /*异常处理*/}
}
}
}
return this.schemaMappings;
}
由此可以知道,XSD可以通过META-INF/spring.schemas
文件的方式进行配置。之后SAX就可以寻找到本地配置的XSD文件来校验XML中标签是否符合规范。
2.3 解析自定义标签代码
2.3.1 解析自定义标签
在解析XML文件的过程中,Spring会对标签进行分类解析:
- 解析Spring默认标签
- 解析自定义标签
Spring标签解析的方法委托给了DefaultBeanDefinitionDocumentReader
类。
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
/**
* 注意,在解析所有标签的前后都有可扩展的前后置方法。
*/
protected void doRegisterBeanDefinitions(Element root) {
//省略...
preProcessXml(root); // 前置方法
parseBeanDefinitions(root, this.delegate);
postProcessXml(root); // 后置方法
this.delegate = parent;
}
/**
* 该方法解析、注册标签的核心方法。
*/
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)) {
parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
}
2.3.2 NamespaceHandler解析自定义标签
由最终的解析方法可知,若判断Element
为自定义标签,则将解析委托给BeanDefinitionParserDelegate
进行操作。
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
// 获取element对应的namespaceUri,并根据namespaceUri找到对应的NamespaceHandler
// 由代码可知,自定义标签的解析工作,由NamespaceHandler的parse()方法完成
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
方法this.readerContext.getNamespaceHandlerResolver()
将获取DefaultNamespaceHandlerResolver
类对象,该对象的resolve方法如下所示:
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;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 此处执行init()方法,将会把自定义标签的解析类注册到Spring
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
} catch () {}
}
}
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
// handlerMappingsLocation="META-INF/spring.handlers"
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}catch (IOException ex) {}
}
}
}
return this.handlerMappings;
}
2.4 NamespaceHandler类介绍
执行完自定义的NamespaceHandler的init方法,通常是注册标签解析器
public class UserNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
如下为NamespaceHandlerSupport
类的实现。通过该方法的parse()
方法直接使用对应的自定义解析类解析自定义标签。
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
// 自定义标签 以及其对应的解析类
private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();
// 将自定义标签和解析类绑定
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
// 从parsers中获取element对应的解析器进行解析。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
return parser;
}
}
2.5 BeanDefinitionParser类
类图结构如下:
代码AbstractBeanDefinitionParser代码如下:
public final BeanDefinition parse(Element element, ParserContext parserContext) {
// parseInternal()方法具体解析属性
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = resolveId(element, definition, parserContext);
String[] aliases = null;
// 是否应该解析ele的"name"属性作为bean的别名
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
// 将解析的BeanDefinition注册到Spring容器
registerBeanDefinition(holder, parserContext.getRegistry());
/* 是否应该在解析bean之后发送一个BeanComponentDefinition事件*/
if (shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
} catch (BeanDefinitionStoreException ex) { }
}
return definition;
}
AbstractSingleBeanDefinitionParser
类的parseInternal()
方法
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
} else {
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
if (parserContext.isNested()) {
builder.setScope(parserContext.getContainingBeanDefinition().getScope());
}
if (parserContext.isDefaultLazyInit()) {
builder.setLazyInit(true);
}
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}