700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Spring源码解析:自定义标签的解析过程

Spring源码解析:自定义标签的解析过程

时间:2019-08-18 10:54:39

相关推荐

Spring源码解析:自定义标签的解析过程

独角兽企业重金招聘Python工程师标准>>>

spring version : 4.3.x

Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解析过程,当然在阅读源码的过程中我们也看到默认标签的解析过程中嵌套了对自定义标签的解析,这是因为默认标签中可以嵌套使用自定义标签,但是这和本篇所要讨论的自定义标签还是有些区别的,上一篇中介绍的自定义标签可以看做是 <bean/> 标签的子标签元素,而本篇所指的标签是与 <bean/> 这类标签平级的自定义标签。

一. 自定义标签的定义和使用方式

在具体开挖源码之前,我们还是来回忆一下自定义标签的定义和使用方式,整体上与上一篇 1.2 小节所定义的方式类似,但还是有些许差别。要自定义标签,分为 5 步:

创建标签实体类定义标签的描述 XSD 文件创建一个标签元素解析器,实现 BeanDefinitionParser 接口创建一个 handler 类,继承自 NamespaceHandlerSupport编写 spring.handlers 和 spring.schemas 文件

这里我们自定义实现一个类似 <alias/> 功能的标签,来为指定的 bean 添加别名。第一步,先创建标签对应的实体:

public class Alias {private String name;private String alias;// 省略 getter 和 setter}

第二步,定义标签的 XSD 文件 custom-alias.xsd:

<?xml version="1.0" encoding="UTF-8"?><schema xmlns="/2001/XMLSchema"targetNamespace="/schema/alias"xmlns:tns="/schema/alias"elementFormDefault="qualified"><element name="alias"><complexType><attribute name="id" type="string"/><attribute name="name" type="string"/><attribute name="parentName" type="string"/><attribute name="c_name" type="string"/><attribute name="c_alias" type="string"/></complexType></element></schema>

第三步,创建标签元素解析器,实现 BeanDefinitionParser 接口,这里我们继承该接口的子接口 AbstractSingleBeanDefinitionParser,并覆盖对应的方法:

public class CustomBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {@Overrideprotected Class<?> getBeanClass(Element element) {return Alias.class;}@Overrideprotected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {String beanName = element.getAttribute("c_name");Assert.hasText(beanName, "The 'name' in alias tag is missing!");Assert.isTrue(parserContext.getRegistry().containsBeanDefinition(beanName), "No bean name " + beanName + " exist!");String alias = element.getAttribute("c_alias");Assert.hasText(beanName, "The 'alias' in alias tag is missing!");String[] aliasArray = alias.replaceAll("\\s+", "").split("[,;]");for (final String ali : aliasArray) {parserContext.getRegistry().registerAlias(beanName, ali);}}}

方法中的逻辑先判断对应的 beanName 是否存在,如果存在的话就建立 beanName 与 alias 之间的映射关系。

第四步,创建标签 handler 类,继承自 NamespaceHandlerSupport,用于注册第三步中定义的标签解析器:

public class CustomNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {this.registerBeanDefinitionParser("alias", new CustomBeanDefinitionParser());}}

第五步,编写 spring.handlers 和 spring.schemas 文件:

spring.handlers

/schema/alias=org.zhenchao.handler.CustomNamespaceHandler

spring.schemas

/schema/alias.xsd=META-INF/custom-alias.xsd

接下来演示一下上述自定义标签的使用方式,首先需要在 <beans/> 标签属性中定义标签的命名空间:

<beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xmlns:myalias="/schema/alias"xsi:schemaLocation="/schema/beans /schema/beans/spring-beans.xsd/schema/alias /schema/alias.xsd"

然后使用我们自定义的标签为已定义的 bean 添加别名:

<!-- my-parent-bean 是一个已定义的 bean --><myalias:alias id="my-alias" c_name="my-parent-bean" c_alias="aaa; bbb"/>

这样我们完成了利用自定义的标签为 my-parent-bean 添加别名,接下来我们开挖自定义标签的解析过程。

二. 自定义标签的解析过程

再来回顾一下我们开始解析标签的入口函数 parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate):

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {// 解析默认标签(beans标签)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);}}

上一篇中我们探究了默认标签的解析过程,也就是 parseDefaultElement(Element element, BeanDefinitionParserDelegate delegate) 方法,接下来我们来探究自定义标签的解析过程,及 parseCustomElement(Element ele) 方法:

public BeanDefinition parseCustomElement(Element ele) {return this.parseCustomElement(ele, null);}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {// 获取标签的命名空间String namespaceUri = this.getNamespaceURI(ele);// 提取自定义标签命名空间处理器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));}

上述方法首先会去获取自定义标签的命名空间定义,然后基于命名空间解析得到对应的 NamespaceHandler,最后调用 handler 对标签进行解析处理,本质上调用的就是前面自定义实现的 doParse() 方法。我们先来看一下自定义标签 NamespaceHandler 的解析过程,位于 DefaultNamespaceHandlerResolver 的 resolve(String namespaceUri) 方法中:

public NamespaceHandler resolve(String namespaceUri) {// 获取所有已注册的handler集合Map<String, Object> handlerMappings = this.getHandlerMappings();// 获取namespaceUri对应的handler全程类名或handler实例Object handlerOrClassName = handlerMappings.get(namespaceUri);if (handlerOrClassName == null) {return null;} else if (handlerOrClassName instanceof NamespaceHandler) {// 已经解析过,直接返回handler实例return (NamespaceHandler) handlerOrClassName;} else {// 未做过解析,则解析对应的类路径classNameString className = (String) handlerOrClassName;try {// 使用反射创建handler实例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);// 调用init()方法namespaceHandler.init();// 缓存解析后的handler实例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);}}}

方法中的逻辑可以概括如下:

从 spring.handlers 获取所有注册的 handler 集合从集合中获取 namespace 对应 handler如果 handler 已经被解析过则返回对应的 handler 实例,否则继续利用反射创建 handler 实例,并初始化调用 handler 的 init() 方法缓存解析后的 handler 实例

上述过程中第 4 步稍微复杂一点,我们来看一下具体过程。我们在 spring.handlers 中会配置 namespaceUri 与对应 handler 全称类名的键值对:

/schema/alias=org.zhenchao.handler.CustomNamespaceHandler

这里会拿到对应 handler 的全称类名,然后基于反射来创建 handler 实例,过程中会设置构造方法为 accessible。接下来就是轮到第五步中的调用 init() 方法,这个方法是由开发人员自己实现的,我们前面的例子中通过该方法将我们自定义的解析器 CustomBeanDefinitionParser 注册到 handler 实例中。接下来就是调用 handler 实例处理自定义标签:

public BeanDefinition parse(Element element, ParserContext parserContext) {// 寻找解析器并进行解析return this.findParserForElement(element, parserContext) // 找到对应的解析器.parse(element, parserContext); // 进行解析(这里的解析过程是开发者自定义实现的)}

这里主要分为获取 handler 实例执行解析两个步骤,其中获取 handler 实例就是依据我们使用的标签名从之前的缓存 map 中拿到对应的对象,然后调用 handler 的 parse(Element element, ParserContext parserContext) 方法执行解析逻辑:

public final BeanDefinition parse(Element element, ParserContext parserContext) {// 1. 创建自定义标签BeanDefinition实例,并调用自定义解析器进行解析处理AbstractBeanDefinition definition = this.parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) { // nested:嵌套的// definition实例存在且不是嵌套的try {// 2. 获取标签的id属性,id属性是必备的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);}// 3. 获取name字段String[] aliases = null;if (this.shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(maDelimitedListToStringArray(name));}}// 4. 将AbstractBeanDefinition转化成BeanDefinitionHolder并注册BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);this.registerBeanDefinition(holder, parserContext.getRegistry());// 5. 事件通知if (this.shouldFireEvents()) {BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);this.postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}} catch (BeanDefinitionStoreException ex) {parserContext.getReaderContext().error(ex.getMessage(), element);return null;}}return definition;}

上述方法中的第一步是整个方法的核心,我们后面细讲,先来看一下第二、三步骤,对于自定义标签来说,id 属性是必备的,此外 Spring 还内置了 name 和 parentName 字段,这些名称是不允许使用的,否则达不到我们预期的结果,笔者第一次使用自定义标签时就踩了坑,用了 name 作为自定义标签属性名,结果就是各种奇怪的结果。

接下来看看第一步的逻辑,位于 AbstractSingleBeanDefinitionParser 的 parseInternal(Element element, ParserContext parserContext) 方法中:

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {// 初始化自定义标签实例BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();// 如果设置了parentNameString parentName = this.getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}// 调用自定义BeanDefinitionParser中的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);// 返回自定义标签的beanDefinition实例return builder.getBeanDefinition();}

上述方法中首先会初始化创建一个 BeanDefinitionBuilder 对象,然后依据配置设置对象的相应属性,其中包括调用我们之前在实现自定义标签解析器 CustomBeanDefinitionParser 时候覆盖的 getBeanClass 方法。然后会调用 doParse 方法,该方法由开发者实现,也是我们解析自定义标签的核心方法:

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {String beanName = element.getAttribute("c_name");Assert.hasText(beanName, "The 'name' in alias tag is missing!");Assert.isTrue(parserContext.getRegistry().containsBeanDefinition(beanName), "No bean name " + beanName + " exist!");String alias = element.getAttribute("c_alias");Assert.hasText(beanName, "The 'alias' in alias tag is missing!");String[] aliasArray = alias.replaceAll("\\s+", "").split("[,;]");for (final String ali : aliasArray) {parserContext.getRegistry().registerAlias(beanName, ali);}}

最后,返回自定义标签对应的 beanDefinition 实例。

分析到这里,Spring 对于配置文件的解析工作已经做完了,容器将一个个 bean 的静态配置解析映射称为 beanDefinition 实例,并注册到容器的 Map 集合中,剩下的就是对 bean 实例的创建和初始化过程了,我们在下一篇中对这一过程的具体实现进行详细探究。

系列文章

Spring源码解析:获取源码Spring源码解析:资源的描述与加载Spring源码解析:IoC容器的基本结构设计Spring源码解析:简单容器中Bean的加载过程初探Spring源码解析:默认标签的解析过程Spring源码解析:自定义标签的解析过程Spring源码解析:Bean实例的创建与初始化Spring源码解析:高级容器的扩展内幕Spring源码解析:循环依赖的探测与处理

鉴于作者水平有限,文中不免有错误之处,欢迎大家批评指正~

同步更新站点:

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。