自定义标签解析

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();
}
posted @ 2019-03-13 09:11  wolf_w  阅读(598)  评论(0编辑  收藏  举报