【Spring源码解析】Spring XML配置自定义标签的解析
1、前言
上两篇文章【Spring源码解析】Spring XML配置默认bean标签解析、【Spring源码解析】Spring xml配置import、alias、beans标签解析 已经完成了XML方式下对所有默认标签的解析,这篇文章说下自定义标签的解析。
- 自定义标签
这里说下什么是自定义标签的解析,除了上述的默认标签都是对自定义标签的解析。
主要作用的是对除默认标签外的一些功能扩展,说到扩展,要说默认的标签能不能实现需要扩展的功能?
肯定也是可以的,但是为了更方便,spring推出了扩展的功能。
这个扩展的功能其实不只是我们在用,spring的功能也有使用,例如MVC注解驱动的标签 <mvc:annotation-driven/> 。
2、实例解析
这里我们使用一个自己定义的标签。
2.1、源码初步解析
首先我们先了解下自定义标签的解析。
先看下之前定义的解析默认标签和自定义标签,区别在于是否使用默认标签的命名delegate.isDefaultNamespace(root) :
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)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
然后看下解析自定义标签,进入方法parseCustomElement :
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return this.parseCustomElement(ele, (BeanDefinition)null);
}
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = this.getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
} else {
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 {
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
}
这里一共有三个步骤:
- getNamespaceURI 获取自定义空间URI。
- this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri) 获取自定义解析器。
- handler.parse 通过解析器进行转换成 BeanDefination 。
以上就是自定义标签的解析。然后我们就根据具体的源码,实现具体的自定义解析过程。
2.2、自定义实现
- 创建自定义Bean对象
package com.fankf.bean;
/**
* @author fankunfeng
* @date 2022-07-09 23:30
*/
public class Student {
private String name;
private String email;
// set/get 省略
}
- XSD 文件(解析标签)自定义
位置在resources目录下创建META-INF文件夹,并在META-INF 下创建stu.xsd。
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.fans.com/schema/user"
xmlns:tns="http://www.fans.com/schema/user"
elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="name" type="string"/>
<attribute name="email" type="string"/>
</complexType>
</element>
</schema>
- META-INF 下创建读取映射文件Spring.schemas
用来查找xsd文件位置。
http://www.fans.com/schema/stu.xsd=META-INF/stu.xsd
- 解析转换对象
public class StudentBeanDefinationParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return Student.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
String email = element.getAttribute("email");
// 将所有的数据放入ParserContext 中,等到所有bean解析完成统一放入BeanFactory中
if (StringUtils.hasText(name))
builder.addPropertyValue("name", name);
if (StringUtils.hasText(email))
builder.addPropertyValue("email", email);
}
}
- META-INF下创建文件Spring.handlers
用来查找handler。
http://www.fans.com/schema/user=com.fankf.spring.StudentNamespaceHandler
- 进行对象到容器
public class StudentNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 以stu 开始的标签交给 StudentBeanDefinationParser 进行解析
registerBeanDefinitionParser("user", new StudentBeanDefinationParser());
}
}
- XML配置文件bean6.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:stu="http://www.fans.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.fans.com/schema/user http://www.fans.com/schema/stu.xsd">
<stu:user id="zs" name="zs1" email="123@123.com"/>
</beans>
- 测试代码
@Test
public void test() {
BeanFactory context = new XmlBeanFactory(new ClassPathResource("bean6.xml"));
Student student = (Student) context.getBean("zs");
System.out.println("【实例使用】" + student.getEmail() + "-" + student.getName());
}
结果: 【实例使用】123@123.com-zs1
以上就完成了自定义标签到解析的过程,我们再次看下解析过程。
2.3、再次进行解析
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = this.getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
} else {
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 {
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
}
- this.getNamespaceURI(ele)
断点调试我们就会发现 this.getNamespaceURI(ele) 返回的就是 我们配置在bean6.xml beans标签中的自定义命名空间 http://www.fans.com/schema/user 。
- this.readerContext.getNamespaceHandlerResolver()
然后 this.readerContext.getNamespaceHandlerResolver() 会获取解析器。这里解析器有多个,其中就有我们自定义的 http://www.fans.com/schema/user -> com.fankf.spring.StudentNamespaceHandler 。
这里也可以看到解析器对应的文件位置META-INF下spring.handlers文件。
- 解析过程
这里就进行解析,进入DefaultNamespaceHandlerResolver##resolve方法:
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> 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<?> 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");
} else {
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
} catch (ClassNotFoundException var7) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
} catch (LinkageError var8) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
}
}
}
分为几个步骤:
- 获取解析对象Object
Map<String, Object> handlerMappings = this.getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
- 进行初始化
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass)
- init
这个就是我们自定义的方法,作用是把user标签的内容交给StudentBeanDefinationParser 进行解析。
当然我们也可以写多个解析的标签,这里只是示例了一个。
@Override
public void init() {
// 以stu 开始的标签交给 StudentBeanDefinationParser 进行解析
registerBeanDefinitionParser("user", new StudentBeanDefinationParser());
}
- 放入处理映射缓存
handlerMappings.put(namespaceUri, namespaceHandler);
- 解析过程 handler.parse
先创建转换上下文 ParserContext。然后进行解析:
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = this.findParserForElement(element, parserContext);
return parser != null ? parser.parse(element, parserContext) : null;
}
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
findParserForElement 的作用是找到我们自定义的解析对象。
我们看到到 parser.parse(element, parserContext) 继续进入:
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = this.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 (this.shouldParseNameAsAliases()) {
String name = element.getAttribute("name");
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
this.registerBeanDefinition(holder, parserContext.getRegistry());
if (this.shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
this.postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
} catch (BeanDefinitionStoreException var8) {
String msg = var8.getMessage();
parserContext.getReaderContext().error(msg != null ? msg : var8.toString(), element);
return null;
}
}
return definition;
}
这里就是四个步骤:
- 先获取id,String id = this.resolveId(element, definition, parserContext)
- 再获取name并处理别名,String name = element.getAttribute(“name”)
- 封装bean对应class 对象BeanDefinitionHolder
- 完成注册 this.registerBeanDefinition(holder, parserContext.getRegistry());
这里其实和之前解析默认标签最后完成注册的流程相同。
- 解析过程总结:
总体流程其实是通过<stu:user/> 中的 beans标签中的配置 xmlns:stu=“http://www.fans.com/schema/user” ,找到处理器,然后通过user标签对应上面 init 方法对应的转换类StudentBeanDefinationParser 解析对象,找到后就进了真正自定义解析的过程,最后完成注册。
3. 总结
对比默认标签bean的解析,这里也是比较简单的,也有可能是万事开头难,完成bean标签的解析,之后就感觉 纵享丝滑 。
再接再厉,加油!共勉!