8Spring 源码学习 ~ 自定义标签的解析
Posted 戴泽supp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8Spring 源码学习 ~ 自定义标签的解析相关的知识,希望对你有一定的参考价值。
自定义标签的解析
前面的章节,我们已经学习过 Spring 默认标签(bean、alias、import 和 beans)的解析,接下来我们来学习自定义标签的解析。
入口:
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))
parseDefaultElement(ele, delegate);
else
delegate.parseCustomElement(ele);
else
delegate.parseCustomElement(root);
所有功能都围绕一句代码 delegate.parseCustomElement(root);
展开。上面的函数可以看出,Spring 拿到一个元素时,首先是根据命名空间进行解析,如果是默认的命名空间,则使用 parseDefaultElement 方法进行解析,否则使用 parseCustomElement 方法解析。我们先来看下自定义标签的使用。
一、自定义标签的使用
1、创建用来接收配置文件的 POJO
package com.luo.spring.guides.helloworld.customtag;
import lombok.Data;
/**
* @author : archer
* @date : Created in 2022/10/25 20:46
* @description :
*/
@Data
@ToString
public class User
private String userName;
private String email;
2、定义一个 XSD 文件描述组件内容
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.lexueba.com/schema/user"
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>
XML Schema 语法可以参考:菜鸟教程 ~ XML Schema 教程 或其他资料
3、实现 BeanDefinitionParser 接口
- 用来解析 XSD 文件中的定义和组件定义
package com.luo.spring.guides.helloworld.customtag;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author : archer
* @date : Created in 2022/10/26 16:42
* @description :
*/
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser
public static final String USER_NAME = "userName";
public static final String EMAIL = "email";
@Override
protected Class<?> getBeanClass(Element element)
return User.class;
@Override
protected void doParse(Element element, BeanDefinitionBuilder beam)
String userName = element.getAttribute(USER_NAME);
String email = element.getAttribute(EMAIL);
if (StringUtils.hasText(userName))
beam.addPropertyValue(USER_NAME, userName);
if (StringUtils.hasText(email))
beam.addPropertyValue(EMAIL, email);
4、创建 Handler,继承 NamespaceHandlerSupport
- 目的是将组件注册到 Spring 容器
package com.luo.spring.guides.helloworld.customtag;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* @author : archer
* @date : Created in 2022/10/26 16:55
* @description :
*/
public class MyNamespaceHandler extends NamespaceHandlerSupport
public static final String USER = "user";
@Override
public void init()
registerBeanDefinitionParser(USER, new UserBeanDefinitionParser());
说明:当遇到自定义标签 <user:aaa
这样类似的以 user 开头的元素,就会把这个元素扔给对应的 UserBeanDefinitionParser 去解析。
5、编写 Spring.handlers 和 Spring.schemas 文件
- 默认位置在工程的
/META-INF/
文件夹下:可通过 Spring 的扩张或修改源码的方式改变路径
spring.handlers
http\\://www.xiaoluoluo.com/schema/user=com.luo.spring.guides.helloworld.customtag.MyNamespaceHandler
spring.schemas
http\\://www.lexueba.com/schema/user.xsd=META-INF/user.xsd
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.xiaoluoluo.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.xiaoluoluo.com/schema/user http://www.xiaoluoluo.com/schema/user.xsd">
<myname:user id="1" userName="aaa" email="nnn"/>
</beans>
Main.java
package com.luo.spring.guides.helloworld.customtag;
import com.luo.spring.guides.helloworld.common.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author : archer
* @date : Created in 2022/10/26 17:16
* @description :
*/
public class Main
public static void main(String[] args)
ApplicationContext bf = new ClassPathXmlApplicationContext("customtag/customtag.xml");
User user = (User) bf.getBean("userbean");
System.out.println(user);
输出:
7、总结
- 1、遇到自定义标签就去 Spring.handlers 和 Spring.schemas 中找对应的 handler 和 XSD,默认位置是 /META-INF/ 下
- 2、找到对应的 handler 和解析元素的 Parser,来完成整个自定义元素的解析。如我们熟知的事务标签
tx(tx:annotation-driven)
。
二、自定义标签解析
了解了自定义标签使用后,我们来学习下自定义标签的解析过程,入口是函数 BeanDefinitionParserDelegate#parseCustomElement,代码如下:
@Nullable
public BeanDefinition parseCustomElement(Element ele)
return parseCustomElement(ele, null);
//containingBd 是父类 bean,对顶层元素的解析应设置为null
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd)
//获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null)
return null;
//根据命名空间找到对应的 NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null)
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
//调用自定义的 NamespaceHandler 进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
我们分步来看下每步是怎么实现的
1、获取标签的命名空间
根据标签锁提供的命名空间,我们可以区分标签是 Spring 中的默认标签还是自定义标签,也可以区分自定义标签中不同标签的处理器。那该如何提取对应元素的命名空间呢?在 org.w3c.dom.Node 中,已经提供了 getNamespaceURI 方法来获取,如下:
@Nullable
public String getNamespaceURI(Node node)
return node.getNamespaceURI();
2、提取自定义标签处理器
根据上面的 parseCustomElement 函数,我们知道提取自定义标签处理器的代码如下:
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
即 DefaultNamespaceHandlerResolver#resolve
@Override
@Nullable
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);
// 判断 handlerClass 的类型是否是 NamespaceHandler 的子类,子接口,本身
if (!NamespaceHandler.class.isAssignableFrom(handlerClass))
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
//实例化(instantiate)类
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
//调用自定义的初始化方法
namespaceHandler.init();
//放入缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
catch (ClassNotFoundException ex)
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
catch (LinkageError err)
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
之前我们在 spring.handlers 中就定义了命名空间与命名空间处理器之间的映射缓存,当获取到命名空间处理器后,就可以进行处理器初始化并解析了。
执行 namespaceHandler.init() 时,会进行自定义 BeanDefinitionParser 的注册,当然你可以注册多个标签解析器,上述示例,只注册了 <myname:user
的标签解析。
注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器。根据逻辑,我们基本可以推断函数 getHandlerMappings 是读取 spring.handlers 配置文件,并将其存在缓存 map 中。源码如下:
private Map<String, Object> getHandlerMappings()
//如果没有被缓存,则开始缓存
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null)
synchronized (this)
handlerMappings = this.handlerMappings;
if (handlerMappings == null)
if (logger.isTraceEnabled())
logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
try
//this.handlerMappingsLocation 在构造函数中已经被初始化为: META-INF/Spring.handlers
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isTraceEnabled())
logger.trace("Loaded NamespaceHandler mappings: " + mappings);
handlerMappings = new ConcurrentHashMap<>(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 handlerMappings;
使用了 PropertiesLoaderUtils#loadAllProperties 来进行配置文件的读取。handlerMappingsLocation 在构造函数中已经被初始化为: META-INF/Spring.handlers。
3、标签解析
提取标签处理器后,接下来是解析标签了,即
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
源码如下:
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext)
//寻找解析器
BeanDefinitionParser parser = findParserForElement(element, parserContext);
//parser.parse(element, parserContext) 进行解析
return (parser != null ? parser.parse(element, parserContext) : null);
@Nullable
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;
而对于 parser.parse(element, parserContext) 方法的处理:
步骤
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext)
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);
// 注册 BeanDefinition
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents())
//通知监听器进行处理
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
catch (以上是关于8Spring 源码学习 ~ 自定义标签的解析的主要内容,如果未能解决你的问题,请参考以下文章