自定义标签的解析
1、自定义标签的使用
扩展spring自定义标签配置大致需要以下几个步骤:
- 创建爱你一个需要扩展的组件
- 定义一个XSD文件描述组件内容
- 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
- 创建一个Handler文件,扩展自NamespaceHandlserSupport,目的是将组件注册到spring容器
- 编写Spring.handlers和Spring.schemas文件
(1) 首先创建一个普通的POJO
public class User {
private String userName;
private String email;
//省略set/get方法
}
(2) 定义一个XSD文件描述组件内容
spring-test.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.lic.com/schema/user" targetNamespace="http://www.lic.com/schema/user"
elementFormDefault="qualified">
<xsd:element name="user">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
<xsd:attribute name="userName" type="xsd:string"></xsd:attribute>
<xsd:attribute name="email" type="xsd:string"></xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
(3) 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
package test;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import bean.User;
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");
//将提取到的数据放到BeanDefinitionBuilder中,带到完成所有bean的解析后统一注册到beanFactory中
if(StringUtils.hasText(userName)){
bean.addPropertyValue("userName", userName);
}
if(StringUtils.hasText(email)){
bean.addPropertyValue("email", email);
}
}
}
(4) 创建一个Hanlder文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器中
package test;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNameSpaceHandler extends NamespaceHandlerSupport{
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
当自定义标签<user:aaa>这样类似于user开头的元素,就会把这个元素交给UserBeanDefinitionParser去解析
(5) 编写spring.handlers和spring.schemas文件,默认位置是在工程的/META-INF/文件下,当然也可以通过Spring的扩展或者修改源码的方式改变路径
- spring.handlers
- spring.schemas
注意: 不能出现空格,必须对冒号进行转义
到这里,自定义的配置就结束了, 而spring加载自定义的大致流程是遇到自定义标签然后就去spring.handlers和spring.schemas中寻找对应的handler和XSD, 默认是在/META-INF/下,进而找到对应的handler以及解析元素的Parser,从而完成整个自定义标签的解析,也就是说自定义表与spring中默认的标准配置不同在于spring将自定义标签解析的工作委托给用户去实现
(6) 创建测试文件,在配置文件中引入对应的命名空间以及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.lic.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lic.com/schema/user http://www.lic.com/schema/user.xsd ">
<myname:user id="testbean" userName="aaa" email="bbb"></myname:user>
</beans>
spring在解析xml的时候,会根据namespaceURI来判断是spring的默认标签还是自定义标签
(7) 测试
package test;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import bean.User;
public class TestClass2 {
@Test
public void testSimpleLoad(){
ApplicationContext bf= new ClassPathXmlApplicationContext("mytest.xml");
User bean=(User) bf.getBean("testbean");
System.out.println(bean.getUserName()+" "+bean.getEmail());
}
}
控制台输出: aaa bbb
2、自定义标签解析
在parseBeanDefinitions方法中,遍历根节点的所有子节点,根据namespaceURI判断是spring的默认标签还是自定义标签,分别进行不同的解析
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans的处理
if (delegate.isDefaultNamespace(root)) {
//获取beans的所有子节点,遍历,对其子节点根据是否使用默认命名空间,使用不同的解析方法
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)) {
//如果是默认命名空间(对bean的处理)
parseDefaultElement(ele, delegate);
}
else {
//如果不是默认命名空间(对bean的处理)
delegate.parseCustomElement(ele);
}
}
}
}
//如果beans不是默认命名空间
else {
delegate.parseCustomElement(root);
}
}
调用BeanDefinitionParserDelegate类中parseCustomElement方法对自定义标签进行解析
delegate.parseCustomElement(ele);
parseCustomElement(Element ele) 方法的实现:
//public class BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele) {
//parseCustomElement(Element ele, BeanDefinition containingBd)
//containingBd为父类bean,对顶层元素的解析应设置为null;
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
/**spring.handler:
* http\://www.lic.com/schema/user=test.MyNameSpaceHandler
* 命名空间 NamespaceHandler
*/
//1.获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
//2.根据命名空间找到对应的NameSpaceHandler,用来解析XSD文件中的定义和组件定义
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//3.调用自定义的NamespaceHandler进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
逻辑分析:
(1) 获取标签的名称空间 例如: xmlns:myname=“http://www.lic.com/schema/user”
(2) 根据名称空间在spring.handler文件中找到对应的NameSpaceHandler,用来解析XSD文件中的定义的组件定义
(3) 调用自定义的NamespaceHandler进行解析
2.1 获取标签的命名空间
标签的解析是从命名空间开始的, 无论是区分spring中默认标签和自定义标签还是区分自定义标签中不同的标签的处理器都是以标签所提供的命名空间为基础的,在org.w3c.dom.Node中已经提供具体的实现
//public class BeanDefinitionParserDelegate
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}
2.2 提取自定义标签处理器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
resolve(String namespaceUri) 方法的实现:
//public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
//获取所有已经配置的handler映射
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);
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的初始化方法
namespaceHandler.init();
//将该handler记录在缓存中,以便下次调用该handler可以直接获取,不用再次转换
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
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);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
//将Properties格式文件合并到Map格式的handlerMappings中
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return this.handlerMappings;
}
逻辑分析:
(1) 在getHandlerMappings()方法中通过读取spring.handlers配置文件,将配置文件的内容缓存到handlerMappings集合中并返回
spring.handlers:
http://www.lic.com/schema/user=test.MyNameSpaceHandler
(2) 如果根据namespaceUri在handlerMappings集合中获取的返回值为空, 则返回null
(3) 如果根据namespaceUri在handlerMappings集合中获取的返回值为实现NamespaceHandler接口的handler处理器, 则强转为NamespaceHandler类型并返回
(4) 如果根据namespaceUri在handlerMappings集合中获取的返回值为handler处理器的类路径, 则根据类路径利用反射进行实例化, 然后执行init方法将自定义BeanDefinitionParser注册到spring容器中, 并将handler处理器替换掉handlerMappings中的类路径, 以便下次解析时不用再次实例化handler处理器
2.3 标签解析
获取到解析器后,spring就可以将解析工作委托给自定义解析器去解析了, 此时的handler已经被实例化为自定义的MyNamespaceHandler, 而MyNamespaceHandler也已经完成了初始化的工作, 但是在自定义命名空间处理器中并没有实现parse方法, 所以推断此处是调用父类中的实现,查看NamespaceHandlerSupport中的parse方法
handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))
parse(Element element, ParserContext parserContext) 方法的实现:
//public abstract class NamespaceHandlerSupport implements NamespaceHandler
public BeanDefinition parse(Element element, ParserContext parserContext) {
//寻找解析器并进行解析操作,在自定义handler中未重写父类中parse()方法,因此此处是调用父类AbstractBeanDefinitionParser中的parse()方法
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//获取元素名称,也就是<myname:user>中的user,若在实例中,此时localName为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;
}
逻辑分析:
解析过程中首先寻找元对应的解析器,进而调用解析器中的parse方法; 结合示例.就是首先获取在MyNamespaceHandler类中的init方法中注册的对应的UserBeanDefinitionParser示例,并调用其parse方法进行进一步解析,同样自定义UserBeanDefinitionParser类中没有重写parse方法,此处是调用父类AbstractBeanDefinitionParser中的parse方法;
parse(Element element, ParserContext parserContext)方法的实现:
//public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser
public final BeanDefinition parse(Element element, ParserContext parserContext) {
//解析element
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = null;
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
//将AbstractBeanDefinition装换成BeanDefinitionHolder并注册
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
//注册
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
//需要通知监听器进行处理
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}
catch (BeanDefinitionStoreException ex) {
parserContext.getReaderContext().error(ex.getMessage(), element);
return null;
}
}
return definition;
}
//public abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
//1.首先进行数据准备,设置beanClass,scope,lazyInit等属性,然后再调用子类重写的doParse方法
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
//获取自定义标签中的class,此时会调用自定义解析器如UserBeanDefinitionParser中的getBeanClass方法;
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
//若子类没有重写getBeanClass方法,则尝试检查子类是否重写getBeanClassName方法
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
if (parserContext.isNested()) {
// Inner bean definition must receive same scope as containing bean.
//若存在父类则使用父类的scope属性
builder.setScope(parserContext.getContainingBeanDefinition().getScope());
}
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
//配置延迟加载
builder.setLazyInit(true);
}
//2.调用子类重写的doParse方法进行解析
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
//自定义UserBeanDefinitionParser
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");
//将提取到的数据放到BeanDefinitionBuilder中,带到完成所有bean的解析后统一注册到beanFactory中
if(StringUtils.hasText(userName)){
bean.addPropertyValue("userName", userName);
}
if(StringUtils.hasText(email)){
bean.addPropertyValue("email", email);
}
}
}
逻辑分析:
(1) 在方法parseInternal中先进行一系列数据准备,然后调用自定义的解析函数对自定义标签进行解析,并将提取到的数据放到BeanDefinitionBuilder中
(2) 将解析后的AbstractBeanDefinition转换为BeanDefinitionHolder
(3) 将BeanDefinitionHolder注册到beanFactory中