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

栏目: Java · 发布时间: 8年前

内容简介: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 方法进行进一步解析。最后就跟 解析默认标签 的套路一样了…

总结

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Android编程权威指南(第3版)

Android编程权威指南(第3版)

比尔·菲利普斯 (Bill Phillips)、克里斯·斯图尔特 (Chris Stewart)、克莉丝汀·马西卡诺 (Kristin Marsicano) / 王明发 / 人民邮电出版社 / 2017-6 / 129.00元

Big Nerd Ranch 是美国一家专业的移动开发技术培训机构。本书主要以其Android 训练营教学课程为基础,融合了几位作者多年的心得体会,是一本完全面向实战的Android 编程权威指南。全书共36 章,详细介绍了8 个Android 应用的开发过程。通过这些精心设计的应用,读者可掌握很多重要的理论知识和开发技巧,获得宝贵的开发经验。 第3 版较之前版本增加了对数据绑定等新工具的介......一起来看看 《Android编程权威指南(第3版)》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具