概要
过度
前一篇文章我们介绍了 :
-
XmlBeanFactory
的基本结构
我们打算从一个例子开始,来介绍 :
- Spring 从 XML 配置文件中加载 BD 并注册
- 根据注册的 BD ,对指定的 Bead 进行实例化操作
上一篇文章我们介绍了 第一部分的前半段: 从 XML配置文件加载 BD 的准备操作和一些外层处理工作。包括:
- 获得文件流
- 对流进行基本处理
- 根据 XML 规范解析成 DOM 树
- 创建上下文【用于在创建过程中出现异常后能及时通知。创建完成后能通知对应的监听器】
- 清理创建 BD 的环境【用于遍历 DOM 树递归时用】
我们基本完成了准备工作。接下来进行最核心的东西,遍历 DOM 树,并根据 DOM 树的内容生成对应的 BD ,并存储起来。
内容简介
本文主要介绍 Spring 自定义的标签的解析操作。包括在 XML 文件中配置的各种属性是怎么映射到对应的 BD 中的。
所属环节
XML 配置文件读取中的 Spring DOM 节点解析。
上下文环节
上文:XML 配置文件读入,XML 文件校验
下文:根据需求创建 Bean实例
源码解析
入口
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
*
* @param root the DOM root element of the document
*/
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);
}
}
我们上面讲到了这里,这里根据命名空间,将解析出来的 DOM 分别交给 Spring 自己的解析器或者在 Spring 中注册的第三方解析器。我们本文只看第一种情况。
Spring 处理的顶级标签总揽
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 处理此类节点,通过配置文件路径引入新的配置文件并进行对应的解析
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 处理此类节点,增加 bean 对别名映射
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 完成对 bean 元素的解析
// 解析完成后,bean Element 会被解析成对应的 BeanDefinition 并和 id,alias
// 一起注册至 Registry
processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
// 在 beans 标签中还可以有 beans 标签,一般是用来进行测试、预发、线上的分组的
// 方便进行对应的 profile 控制。当然,这里就直接递归调用即可,ele 传进去做 containerElement
doRegisterBeanDefinitions(ele);
}
}
上面是对四种标签的处理方法。我们将依次介绍,每个标签的处理思路都是比较顺畅的:
标签解析
总体思路很简单,就是明确一下资源,然后调用loadBeanDefinitions
方法完成加载。
因为在loadBeanDefinitions
中有防止资源重复加载的机制,所以直接直接调即可,贼鸡儿简单。
/**
* 将 import 标签指明地址的资源加载进来并完成在此 Factory 中的注册
*/
protected void importBeanDefinitionResource(Element ele) {
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 没有设置对应的属性。直接返回
if (!StringUtils.hasText(location)) {
// ReaderContext 是一个管理各种消息通道、事件通道、注册器的一个上下文环境
getReaderContext().error("Resource location must not be empty", ele);
return;
}
// 将指明地址的字符串进行解析,例如 "${user.dir}" 做一下替换
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set actualResources = new LinkedHashSet<>(4);
// Discover whether the location is an absolute or relative URI
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}
// Absolute or relative?
if (absoluteLocation) {
try {
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
} else {
// No URL -> considering resource location as relative to the current file.
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
标签解析
的功能是对 Bean 增加一个别名映射,在后面实例化 Bean 时比如你想实例化 A,可以传 Bean A 的 id 或者 别名 都可以。
他的实现逻辑也很简单:
在
BeanFactory
实现了一个接口AliasRegistry
,他通过继承SimpleAliasRegistry
完成对此功能的实现。SimpleAliasRegistry
就是用Map
实现的一个alias--->id/alias
的一个映射。在
SimpleAliasRegistry
中做了防止循环依赖的操作。
所以我们只需要直接调用一下存储操作即可。
protected void processAliasRegistration(Element ele) {
// 做个判空,没问题就直接丢进去
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
标签解析
这个没得说,直接递归调用doRegisterBeanDefinitions
即可,记得将root
传进去,表示这个
是有上级 container
的,有些profile
相关的会参考container
的相关配置。
标签解析
/**
* 解析 element 生成 BD 并进行注册。
*
* Element ele : 要解析的 DOM 节点
* BeanDefinitionParserDelegate delegate:一个对象,可以拿到:
* 1. 这个 节点上面的 节点的相关配置
* 2. 上下文环境以及对应的各种监听器
* 3. 这个 节点上面的 再上面的 【理论上能一直递归到最上层】
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 将 Element 元素转换成包括 BeanDefinition 和对应的 id、alias 的 BeanDefinitionHolder
// 方便统一进行注册
// 【返回这个而不是 BD 的原因是 BD 中不含有 ID、Name(alias),但是我们配置的 XML 元素中有】
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 对生成的 BeanDefinitionHolder 进行一些装饰
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 对该 bean 进行注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
// 新 bean 注册完成,触发对应的监听器
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
整体来说思路还是很简单的:
- 解析
Element
,拿到解析好的BeanDefinitionHolder
- 修饰解析好的
BeanDefinitionHolder
- 对处理好后的东西进行 BD 的注册
- 进行监听器通知
我们按照下面的思路来看,先看一下Element
的解析:
解析 Element
入口:delegate.parseBeanDefinitionElement(ele)
解析 Element
,拿到解析好的BeanDefinitionHolder
。
注意了,这里我们进入了BeanDefinitionParserDelegate
,能自动拿到环境的相关信息。
/**
* 解析输入的 Element 返回 holder,虽然这个可能有父 Element ,但是我们先假装没有,将注意力放
* 到解析元素上。
*
* 上下文的东西我们后面会对生成的东西进行修饰,到时候就同步了。
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
/**
* 解析入参,如果有错误可能返回 null 。
* 当然,如果遇到问题,也会把问题报告到上下文中注册的错误收集器中。
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 拿到对应的 Id 和别名,放到 holder 中
// 剩下的属性就是 BD 中都有对应的了
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 剩下的属性就是 BD 中都有对应的了,
// 直接将 ele 中的属性转化成 BeanDefinition 的属性,生成对应的 BD
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// 如果没有设置 id 和 name 也就是说没有设置任何可以做默认属性的东西,
// 那就根据 bean 的相关信息自行生成 id
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
} else {
// 这两个归根结底都是调用的 BeanDefinitionReaderUtils.generateBeanName
// 只是这里传的 isInnerBean 是 false ,即 containingBean 不存在,所以不是内部 bean
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
// 按照生成的逻辑缺失都符合
//TODO 但是感觉只判断不为空就行了吧,毕竟根据逻辑, beanClassName 存在,就是返回这种的 name 格式
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName); // 默认把 bean 的 className 加到别名中去了
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
} catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
思路比较顺畅:
- 先读取 Id 和 Name
- 将 BD 中的东西读出来
- 看看如果 Id 和 Name 都没有,根据 BD 中的 情况做一下默认的生成工作
- 包装成
BeanDefinitionHolder
并返回
读取 BD 中的东西
就是读属性,然后设置进去。都是模版代码,没得说,过吧。
/**
* Parse the bean definition itself, without regard to name or aliases. May return
* {@code null} if problems occurred during the parsing of the bean definition.
*/
// 从 element 中取得 bean 定义的相关信息
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
} catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
} catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
} catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
} finally {
this.parseState.pop();
}
return null;
}
如果 Id 和 Name 缺失,根据 BD 自动生成
这里采用了两种生成 Id 的方法,一种是使用 Core 包中的 Util 类为有 container
的 BD 生成 Id;另一种是使用在上下文中注册的工具方法生成 Id 。
第二种的内部实现根据各自情况定,默认情况下还是用的 Core 包中的 Util 类。绕了一圈只是为了留下一个方便扩展的配置而已。
修饰解析好的BeanDefinitionHolder
入口:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)
根据上下文环境修饰一下解析好的BeanDefinitionHolder
。
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder;
// Decorate based on custom attributes first.
// 装饰本 Element 的属性
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
// Decorate based on custom nested elements.
// 装饰 Element 下面的节点
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
修饰 BeanDefinitionHolder
要通过在 上下文环境中设置的NamespaceHandlerResolver
进行修饰,这个我们后面看完自定义的元素解析之后再回头看。
public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(node);
// 只解析默认命名空间的元素
if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) { // 有 handler 就直接调用
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
if (decorated != null) {
return decorated;
}
}
// 这块有点蒙
else if (namespaceUri.startsWith("http://www.springframework.org/")) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
}
// 不解析
else {
// A custom namespace, not to be handled by Spring - maybe "xml:...".
if (logger.isDebugEnabled()) {
logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}
对处理好后的东西进行 BD 的注册
入口:BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
进行监听器通知
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))
后面统一看一下这个信息传输机制。
问题遗留
- 通过在 上下文环境中设置的
NamespaceHandlerResolver
进行生成好的bdHolder
修饰 - 上下文的监听器机制