自定义标签的解析
当Spring完成从配置文件到Document的转换并提取对应的root后,将开始解析所有元素,这一过程分为默认标签和自定义标签的解析
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if(delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
String namespaceUri = delegate.getNamespaceURI(ele);
if(delegate.isDefaultNamespace(namespaceUri)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
从上面的函数可以看出,当Spring拿到一个元素时首先要做的是根据命名空间进行解析,如果是默认的命名空间,则使用parseDefaultElement方法进行元素解析,否则使用parseCustomElement方法进行解析
自定义标签的使用
Spring提供了可扩展Schema的支持,扩展Spring自定义标签配置大致需要以下几个步骤:
- 创建一个需要扩展的组件
- 定义一个XSD文件描述组件内容
- 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
- 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
- 编写Spring.handlers和Spring.schemas文件
示例:
(1)创建一个普通的POJO
public class User{
private String userName;
private String email;
//省略set/get方法
}
(2)定义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>
在上面的XSD文件中描述了一个新的targetNamespace,并在这个空间中定义了一个name为user的element,user有3个属性id、userName和email
(3)创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
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)创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
public class MyNamespaceHandler extends NamespaceHandlerSupport{
public void init(){
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
当遇到自定义标签<user:aaa这样类似于以user开头的元素,就会把这个元素扔给对应的UserBeanDefinitionParser去解析
(5)编写Spring.handlers和Spring.schemas文件,默认位置是在工程的/META-INF/文件夹下
Spring.handlers
http://www.lexueba.com/schema/user=test.customtag.MyNamespaceHandler
Spring.schemas
http://www.lexueba.com/schema/user.xsd=META-INF/String-test.xsd
到这里,自定义的配置就结束了,而Spring加载自定义的大致流程是遇到自定义标签后就去Spring.handlers和Spring.schemas中去找对应的handler和XSD,默认位置是/META-INF/下,进而有找到对应的handler以及解析元素的Parser,从而完成了整个自定义元素的解析,也就是说自定义与Spring中默认的标准配置不同在于Spring将自定义标签解析的工作委托给了用户去实现
(6)创建测试配置文件,在配置文件中引入对应的命名空间以及XSD后,便可以直接使用自定义标签了
<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-2.0.xsd"
http://www.lexueba.com/schema/user
http://www.lexueba.com/schema/user.xsd">
<myname:user id="testbean" userName="aaa" email="bbb" />
</beans>
(7)测试
public static vod main(String[] args){
ApplicationContext bf = new ClassPathXmlApplicationContext("test/customtag/test.xml");
User user = (User)bf.getBean("testBean");
System.out.println(user.getUserName() + "," + user.getEmail());
}
自定义标签解析
public BeanDefinition parseCustomElement(Element ele) {
return this.parseCustomElement(ele, (BeanDefinition)null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//获取对应的命名空间
String namespaceUri = this.getNamespaceURI(ele);
//根据命名空间找到对应的NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if(handler == null) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
} else {
//调用自定义的NamespaceHandler进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
获取标签的命名空间
标签的解析是从命名空间开始的,无论是Spring中默认标签还是自定义标签,不同标签的处理器都是以标签所提供的命名空间为基础的,而至于如何提取对应元素的命名空间,在org.w3c.dom.Node中已经提供了方法直接调用
public String getNamespaceURI(Node node){
return node.getNamespaceURI();
}
提取自定义标签处理器
有了命名空间,就可以进行NamespaceHandler的提取了,NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),在readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化为了DefaultNamespaceHandlerResolver的实例,所以,这里调用的resolve方法其实调用的是DefaultNamespaceHandlerResolver类中的方法
//DefaultNamespaceHandlerResolver.java
public NamespaceHandler resolve(String namespaceUri) {
//获取所有已经配置的handler映射
Map handlerMappings = this.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 err = ClassUtils.forName(className, this.classLoader);
if(!NamespaceHandler.class.isAssignableFrom(err)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
} else {
//初始化类
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(err);
//调用自定义的NamespaceHandler的初始化方法
namespaceHandler.init();
//记录在缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
} catch (ClassNotFoundException var7) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7);
} catch (LinkageError var8) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8);
}
}
}
Spring会根据在Spring.handlers文件中配置的命名空间与命名空间处理器的映射关系找到匹配的处理器,而寻找匹配的处理器就是在上面函数中实现的,当获取到自定义的NamespaceHandler之后就可以进行处理器初始化并解析了
示例中对于命名空间处理器的内容如下:
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init(){
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
当得到自定义命名空间处理后会马上执行namespaceHandler.init()来进行自定义BeanDefinitionParser的注册
注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器进行解析
getHandlerMappings的主要功能就是读取Spring.handlers配置文件并将配置文件缓存在map中
private Map<String, Object> getHandlerMappings() {
//如果没有被缓存则开始进行缓存
if(this.handlerMappings == null) {
synchronized(this) {
if(this.handlerMappings == null) {
try {
//this.handlerMappingsLocation在构造函数中已经被初始化为:META-INF/Spring.handlers
Properties ex = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Loaded NamespaceHandler mappings: " + ex);
}
ConcurrentHashMap handlerMappings = new ConcurrentHashMap();
//将Properties格式文件合并到Map格式的handlerMappings中
CollectionUtils.mergePropertiesIntoMap(ex, handlerMappings);
this.handlerMappings = handlerMappings;
} catch (IOException var4) {
throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", var4);
}
}
}
}
return this.handlerMappings;
}
工具类PropertiesLoaderUtils对属性handlerMappingsLocation进行了配置文件的读取,handlerMappingsLocation被默认初始化为”META-INF/Spring.handlers”
标签解析
得到了解析器以及要分析的元素后,Spring就可以将解析工作委托给自定义解析器去解析,在Spring中的代码为:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
parse方法是在父类NamespaceHandlerSupport中实现的,代码如下
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}
解析过程中首先是寻找元素对应的解析器,进而调用解析器中的parse方法,那么结合示例来讲,其实就是首先获取在MyNameSpaceHandler类中的init方法中注册的对应的UserBeanDefinition实例,并调用其parse方法进行进一步解析
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 = (BeanDefinitionParser)this.parsers.get(localName);
if(parser == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
而parse方法的处理
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
if(definition != null && !parserContext.isNested()) {
try {
String ex = this.resolveId(element, definition, parserContext);
if(!StringUtils.hasText(ex)) {
parserContext.getReaderContext().error("Id is required for element \'" + parserContext.getDelegate().getLocalName(element) + "\' when used as a top-level tag", element);
}
String[] aliases = new String[0];
String name = element.getAttribute("name");
if(StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
//将AbstractBeanDefinition转换为BeanDefinitionHolder并注册
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, ex, aliases);
this.registerBeanDefinition(holder, parserContext.getRegistry());
if(this.shouldFireEvents()) {
//需要通知监听器进行处理
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
this.postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
} catch (BeanDefinitionStoreException var9) {
parserContext.getReaderContext().error(var9.getMessage(), element);
return null;
}
}
return definition;
}
在这个函数中大部分的代码是用来处理将解析后的AbstractBeanDefinition转化为BeanDefinitionHolder并注册的功能,而真正去做解析的事情委托给了函数parseInternal,正是这句代码调用了我们自定义的解析函数
在parseInternal中并不是直接调用自定义的doParse函数,而是进行了一系列的数据准备,包括对beanClass、scope、lazyInit等属性的准备
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = this.getParentName(element);
if(parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
//获取自定义标签中的class,此时会调用自定义解析器如UserBeanDefinitionParser中的getBeanClass方法
Class beanClass = this.getBeanClass(element);
if(beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
} else {
//若子类没有重写getBeanClass方法则尝试检查子类是否重写getBeanClassName方法
String beanClassName = this.getBeanClassName(element);
if(beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
if(parserContext.isNested()) {
//若存在父类则使用父类的scope属性
builder.setScope(parserContext.getContainingBeanDefinition().getScope());
}
if(parserContext.isDefaultLazyInit()) {
//配置延迟加载
builder.setLazyInit(true);
}
//调用子类重写的doParse方法进行解析
this.doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
this.doParse(element, builder);
}
自定义标签的处理过程同样是按照Spring中默认标签的处理方式进行,包括创建BeanDefinition以及进行相应默认属性的设置,对于这些工作Spring都默默地帮我们实现了,只是暴露出一些接口来供用户实现个性化的业务