阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。
引子
系列1 - bean 标签解析:
4、xml配置文件解析之【默认】命名空间【标签】的解析.md
5、xml配置文件解析之【自定义】命名空间【标签】的解析.md
系列2 - bean 获取: getBean() 做了什么
一句话概括:
【只读取配置内容,并注册管理】
因为spring-beans 支持的只是将默认配置读取,并解析到 Bean 这种比较通用的逻辑。
如果我们希望对 xml 注入的bean的 成员变量 进行:
等等个性化操作时,spring 官方实现的标签就不太适合做这种操作了,所以我们需要根据自己的业务去自定义标签。
书接上回,上文讲了 spring 默认命名空间标签解析:
本文将从 自定义标签的解析开始展开。
稍微回顾下,这里的:【delegate】 的来历,它是:DefaultBeanDefinitionDocumentReader 类的成员变量,在不做提前配置的时候,它的默认值是:null
再看下图对 delegate 的初始化操作,所以前文的两个猜想,已经有了答案了,delegate 只有下图中唯一的一种类型。
对于自定义标签的解析,也只是在其上增加了一些配置,至于具体是什么样的配置,让 delegate 可以支持对,用户自定义标签的解析呢?
如下章节将围绕这个主题展开:
我们基于,已知逻辑继续阅读代码:
delegate.parseCustomElement(ele); // 自定义命名空间(书签)
图中标注的3行代码表示如下3个行为:
没啥好说的,就是简单xml文件内容中,关于自定标签的命名空间信息读取。
目光放到这里:
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
记住这个类名:[NamespaceHandler] 世界线会收束的,我们还会见到它的。
readerContext 是谁? 它从哪里来?
回顾下前文代码就知道了:
【tag:问题1】
NamespaceHandlerResolver.resolve() 干了啥?
【tag:问题1】
再追踪 NamespaceHandler.parse(xx, xxx) 方法,我们发现,走不下去了:
【tag:问题2】
按下 【Ctrl + Alt + 鼠标左键】 发现不止一个类实现了NamespaceHandler接口,我们并不能确定下一步走向何方了。
【tag:问题2】
为了解答 【3.2节】 和 【3.3节】 提出问题,在第四章中,我们将简单演示下,自定义标签的使用,看完第四章,这两个问题将得到初步解答。
同默认命名空间标签解析一样,这里只讨论从自定义标签,解析成 BeanDefinition 的过程
package org.springframework.custom.bean;
public class CustomModelBean {
private String modelName;
private String desc;
private String action;
public String getAction() {
return action;
}
public void setAction(String action) { this.action = action; }
public String getModelName() { return modelName; }
public void setModelName(String modelName) { this.modelName = modelName; }
public String getDesc() { return desc; }
public void setDesc(String desc) { this.desc = desc; }
}
package org.springframework.custom.parser;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.custom.bean.CustomModelBean;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* 继承抽象类 AbstractSingleBeanDefinitionParser 实现自定义标签的解析逻辑
* - 它会负责将xml 配置的标签解析成 BeanDefinition的形式,最终注册到 BeanFactory [ > BeanDefinitionRegistry]
*/
public class CustomModelParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return CustomModelBean.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String modelName = element.getAttribute("modelName");
String desc = element.getAttribute("desc");
String action = element.getAttribute("action");
// 将从自定义标签提取到的属性注册到:BeanDefinitionBuilder;最终会注册到 BeanFactory中
if (StringUtils.hasText(modelName)) {
builder.addPropertyValue("modelName", modelName);
}
if (StringUtils.hasText(desc)) {
builder.addPropertyValue("desc", desc);
}
if (StringUtils.hasText(action)) {
builder.addPropertyValue("action", action);
}
}
}
这里,我们自定的标签名是:
我们可以在xml 文件里使用它去配置 自定义的Bean
package org.springframework.custom.handler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.custom.parser.CustomModelParser;
public class CustomModelHandler extends NamespaceHandlerSupport {
@Override
public void init() {
/**
* 注册自定义标签的解析器到 BeanFactory 中
*/
registerBeanDefinitionParser("custom-model", new CustomModelParser());
}
}
前文有提到过,spring 会通过xsd 或者 dtd 文件,对配置的xml进行校验。
如下的 xsd 文件,声明了,我们的自定义标签的属性;我们定义的:custom-bean 标签在被解析前,需要通过如下 xsd文件的校验。
【至于为啥要做校验,为了防止xml注入攻击应该是原因之一】
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- 这里的案例只是伪代码,这里贴的命名空间也是伪代码 -->
<xsd:schema xmlns="http://www.bokerr.com/schema/custom-bean"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.bokerr.com/schema/custom-bean"
elementFormDefault="qualified">
<xsd:element name="custom-bean">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="modelName" type="xsd:string"/>
<xsd:attribute name="desc" type="xsd:string"/>
<xsd:attribute name="action" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
在 spring.beans 模块下找到:Spring.handlers 和 Spring.schemas
我们这两个文件中追加我们自己的配置:
第四章所描述的就是我们的: custom-bean 标签的配置过程了。
第三章结尾的问题,也能答上来了,这里我们需要关注的 NamespaceHandler 的实现类是:
而我们在 CustomModelHandler 类中重写的就是 NamespaceHandlerSupport 的 init() 方法,我们在这里注入了,自定义标签的解析器:
【Ctrl + Alt + 鼠标左键】 找到NamespaceHandlerResolver接口resolve方法唯一实现
getHandlerMappings 的代码就不放了,刚兴趣可以自己看.
里边的内容是:
从第四章中提到的:Spring.handlers 文件中,根据标签命名空间,
获取到我们自定义的:
CustomModelHandler
经过上一环节,这里我们拿到了 NamespaceHandler 继续深入 parse() 方法:
因为我们继承的 NamespaceHandlerSupport 抽象类,所以这里直接看 它的parse 方法
这里一共两件事:
parser = findParserForElement(element, parserContext); 方法
提取通过 CustomModelHandler 注入容器的,自定义标签解析器:
(parser != null ? parser.parse(element, parserContext) : null)
当解析器不为空,执行解析的 parse 方法,自定义的 CustomModelParser 类覆写的超类方法是:
根据 CustomModelParser 的类图,我们从而知道, parser.parse() 的实现逻辑在:
到此为止,调用到我们自己重写的 doParse() 方法了,
最终,上述的:parseInternal() 解析得到 BeanDefinition 后,会将其注册到 容器中。
这里的 parserContext 的来路也可以追溯。
至于 readerContext 的来路,我想不用我再说了吧,前文已经追溯过了,它打哪来:
自定义标签的学习也到此为止了。