转载

Spring解密 - 自定义标签与解析

Spring 是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是 Java工作中 必备技能之一…

前言

在 上一节 Spring解密 - 默认标签的解析 中,重点分析了 Spring 默认标签 是如何解析的,那么本章继续讲解标签解析,着重讲述如何对 自定义标签 进行解析。

自定义标签

在讲解 自定义标签解析 之前,先看下如何自定义标签

定义 XSD 文件

定义一个 XSD 文件描述组件内容

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.battcn.com/schema/battcn"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            targetNamespace="http://www.battcn.com/schema/battcn"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans" />

    <xsd:element name="application">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="name" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
  • 声明命名空间: 值得注意的是 xmlnstargetNamespace 可以是不存在,只要映射到指定 XSD 就行了。
  • 定义复合元素: 这里的 application 就是元素的名称,使用时 <battcn:application id="battcn"/>
  • 定义元素属性: 元素属性就是 attribute 标签,我们声明了一个必填的 name 的属性,使用时 <battcn:application id="battcn" name="Levin"/>

定义解析规则

1.创建一个类实现 BeanDefinitionParser 接口(也可继承 Spring 提供的类),用来解析 XSD 文件中的定义和组件定义

public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class getBeanClass(Element element) {
		// 接收对象的类型 如:String name = (String) context.getBean("battcn");
        return String.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
		// 在 xsd 中定义的 name 属性
        String name = element.getAttribute("name");
        bean.addConstructorArgValue(name);
    }
}

这里创建了一个 ApplicationBeanDefinitionParser 继承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser 的子类) , 重点就是重写的 doParse ,在这个里面解析 XML 标签的,然后将解析出的 value(Levin) 通过构造器方式注入进去

2.创建一个类继承 NamespaceHandlerSupport 抽象类

public class BattcnNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser());
    }

}

BattcnNamespaceHandler 的作用特别简单,就是告诉 Spring 容器,标签 <battcn:application /> 应该由那个解析器解析( 这里是我们自定义的: ApplicationBeanDefinitionParser ),负责将组件注册到 Spring 容器

3.编写 spring.handlersspring.schemas 文件

文件存放的目录位于 resources/META-INF/文件名

spring.handlers
http/://www.battcn.com/schema/battcn=com.battcn.handler.BattcnNamespaceHandler
spring.schemas
http/://www.battcn.com/schema/battcn.xsd=battcn.xsd

4.使用自定义标签

申明 bean.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:battcn="http://www.battcn.com/schema/battcn"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.battcn.com/schema/battcn
       http://www.battcn.com/schema/battcn.xsd">

    <battcn:application id="battcn" name="Levin"/>

</beans>

创建一个测试类,如果看到控制台输出了 Levin 字眼,说明自定义标签一切正常

public class Application {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        String name = (String) context.getBean("battcn");
        System.out.println(name);

    }
}

5.如图所示

Spring解密 - 自定义标签与解析

源码分析

自定义标签解析入口

public class BeanDefinitionParserDelegate {

	@Nullable
	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		// 获取命名空间地址 http://www.battcn.com/schema/battcn
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		// NamespaceHandler 就是 自定义的 BattcnNamespaceHandler 中注册的 application
		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));
	}

}

与默认标签解析规则一样的是,都是通过 getNamespaceURI(Node node) 来获取命名空间,那么 this.readerContext.getNamespaceHandlerResolver() 是从哪里获取的呢?我们跟踪下代码,可以发现在项目启动的时候,会在 XmlBeanDefinitionReader 将所有的 META-INF/spring.handles 文件内容解析,存储在 handlerMappers(一个ConcurrentHashMap) 中,在调用 resolve(namespaceUri) 校验的时候在将缓存的内容提取出来做对比

public class XmlBeanDefinitionReader {

	public NamespaceHandlerResolver getNamespaceHandlerResolver() {
		if (this.namespaceHandlerResolver == null) {
			this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
		}
		return this.namespaceHandlerResolver;
	}

}

resolve

1.加载指定的 NamespaceHandler 映射,并且提取的 NamespaceHandler 缓存起来,然后返回

public class DefaultNamespaceHandlerResolver {

	@Override
	@Nullable
	public NamespaceHandler resolve(String namespaceUri) {
		Map<String, Object> handlerMappings = getHandlerMappings();
		// 从 handlerMappings 提取 handlerOrClassName
		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);
				// Handler 初始化
				namespaceHandler.init();
				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);
			}
		}
	}

}

标签解析

加载完 NamespaceHandler 之后, BattcnNamespaceHandler 就已经被初始化为 了,而 BattcnNamespaceHandler 也调用了 init() 方法完成了初始化的工作。因此就接着执行这句代码: handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); 具体标签解。

public class NamespaceHandlerSupport {

	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);
	}

	@Nullable
	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		// 解析出 <battcn:application /> 中的  application
		String localName = parserContext.getDelegate().getLocalName(element);
		BeanDefinitionParser parser = this.parsers.get(localName);
		if (parser == null) {
			parserContext.getReaderContext().fatal(
					"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
		}
		return parser;
	}


}

简单来说就是从 parsers 中寻找到 ApplicationBeanDefinitionParser 实例,并调用其自身的 doParse 方法进行进一步解析。最后就跟 解析默认标签 的套路一样了…

总结

熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走…

原文  http://blog.battcn.com/2018/01/12/spring/spring-3/
正文到此结束
Loading...