基本概念

BeanDefinitionDocumentReader ,该类的作用有两个,完成 BeanDefinition 的解析和注册 。

  • 解析:其实是解析 Ddocument 的内容并将其添加到 BeanDefinition 实例的过程。

  • 注册:就是将 BeanDefinition 添加进 BeanDefinitionHolder 的过程,这样做的目的是保存它的信息。

下面来看它的接口定义,该接口只定义了一个方法负责完成解析和注册的工作:

public interface BeanDefinitionDocumentReader {

    void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)throws BeanDefinitionStoreException;

}

再来看它的继承关系,默认只有一个实现类:


源码分析

接下来来看 DefaultBeanDefinitionDocumentReader 类中的 registerBeanDefinitions 方法。

首先来看该方法的入参:

  • Document:代指 Spring 的配置文件信息,通过 BeanDefinitionReader 解析 Resrouce 实例得到。

  • XmlReaderContext :主要包含了 BeanDefinitionReader 和 Resrouce 。

再来看它的具体流程:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext; // 日志输出... // 取得根元素,即 XML 文件中的 <beans> 标签
Element root = doc.getDocumentElement(); // 关键 -> 继续 Document 的解析
doRegisterBeanDefinitions(root);
}

关于 doRegisterBeanDefinitions,该方法的主要作用有:

  • 创建 BeanDefinitionParserDelegate 对象,用于将 Document 的内容转成 BeanDefinition 实例,也就是上面提到的解析过程,BeanDefinitionDocumentReader 本身不具备该功能而是交给了该类来完成。

  • 取得 beans 标签中 profile 的属性内容,该标签主要用于环境的切换。例如开发过程中,一般存在测试环境和正式环境,两者之间可能存在不同的数据源。若想要实现环境的快速切换,就可以利用 profile 来配置。具体实现这里暂不探究。

protected void doRegisterBeanDefinitions(Element root) {
// 创建 delegate 对象
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent); // 验证 XML 文件的命名空间,即判断是否含有 xmlns="http://www.springframework.org/schema/beans"
if (this.delegate.isDefaultNamespace(root)) { // 解析 profile
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
} // 空方法
preProcessXml(root); // 关键 -> 开始解析 Bean 定义
parseBeanDefinitions(root, this.delegate); // 空方法
postProcessXml(root); this.delegate = parent;
}

下面开始通过遍历取得 beans 元素的所有子节点。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 验证 XML 文件的命名空间
if (delegate.isDefaultNamespace(root)) {
// 取得 <beans> 的所有子节点
NodeList nl = root.getChildNodes(); // 遍历 <beans> 的子节点
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i); // 判断节点是不是 Element 类型
if (node instanceof Element) {
Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) {
// 关键 -> 解析 <beans> 子节点的内容
parseDefaultElement(ele, delegate); }else {
// 解析自定义元素,暂不探究
delegate.parseCustomElement(ele);
}
}
}
}else {
delegate.parseCustomElement(root);
}
}

在拿到了子节点后,开始解析 beans 标签的子节点,常见的标签有 import,alias,bean,beans 等。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 处理 <import> 标签,将资源进行合并再统一解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 处理 <alias> 标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 关键-> 处理 <bean> 标签
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 处理 <beans> 标签,回到开始解析 document 的地方
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

这里关键来探究下 bean 的解析过程:

  • 上面提到 bean 标签的具体解析工作交给 BeanDefinitionParserDelegate 类来完成。

  • 在完成解析取得 BeanDefinition(被添加进了 BeanDefinitionHolder ) 对象之后利用 BeanDefinitionRegistry 完成注册过程。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

    // 关键 -> ①交给委托类 delegate 来完成解析过程 ,并返回 BeanDefinitionHolder 对象
// 该对象存储了 BeanDefinition 的基本信息
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); // 交给委托类 delegate 来完成修饰过程,这里暂不探究
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 关键 -> ②注册最后的装饰实例
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); }catch (BeanDefinitionStoreException ex) {
//错误输出...
} // Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

观察上述代码,发现 DefaultBeanDefinitionDocumentReader 的主要职责是解析 Document ,取得配置文件(这里指 xml )中定义的标签内容;而解析标签的过程交给 BeanDefinitionParserDelegate 类完成;注册过程交给了 BeanDefinitionRegistry 接口来完成。


1.BeanDefinition 解析

在 DefaultBeanDefinitionDocumentReader 关于 bean 标签的解析方法如下:

BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  • 1

下面来看下 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement 方法。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
} public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 取得 <bean> 标签的 id 属性
String id = ele.getAttribute(ID_ATTRIBUTE); // 取得 <bean> 标签的 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 创建 List 用于存方法 alsas(别名)集合
List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) {
// 将 nameArr 按照(,)或(;)分割成数组
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); // 添加进行别名集合
aliases.addAll(Arrays.asList(nameArr));
} // 判断 id 是否为空,若为空则取别名集合的第一个元素当作 id ,并将其从别名集合当中移除
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
// 日志输出...
} // 检查 id(标识)和 alias(名别)是否唯一
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
} // 关键 -> 解析 <bean> 标签的 class 属性 以及相关特性标签,并将其添加进 beanDefinition 返回
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { // 判断是否存在 beanName(id)
if (!StringUtils.hasText(beanName)) {
try { if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
}else { // 由 Spring 自动生成 beanName(id)
beanName = this.readerContext.generateBeanName(beanDefinition); // 取得 bean 的 完整类名可用
String beanClassName = beanDefinition.getBeanClassName(); // 将 beanName 添加进 alais 集合
if (beanClassName != null &&
beanName.startsWith(beanClassName) &&
beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName);
}
} // 日志输出... }catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
} // 将 aliases 集合数组化
String[] aliasesArray = StringUtils.toStringArray(aliases); // 关键 -> 返回一个 BeanDefinitionHolder 实例,用于存储信息
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
} return null;
}

再来看解析 bean 标签的 class 属性以及相关特性标签,并将其添加进 beanDefinition 返回的具体过程:

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {

    // 入栈操作,往 parseState 中添加一个 新建的 BeanEntry
this.parseState.push(new BeanEntry(beanName)); // 取得 <bean> 标签的 class 属性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
} try {
// 取得 <bean> 标签的 parent 属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
} // 根据 class,parent 的属性值创建一个 BeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 取得 <bean> 标签的其他特性属性,并添加进 BeanDefinition 。如:
// scope、
// lazy-init
// autowire
// primary、autowire-candidate
// depends-on、dependency-check
// init-method、destroy-method
// factory-method、factory-bean
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 解析 <mate> 标签
parseMetaElements(ele, bd); // 解析 <lookup-method> 标签
parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // 解析 <replaced-method> 标签
parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析 <constructor-arg> 标签
parseConstructorArgElements(ele, bd); // 解析 <property> 标签
parsePropertyElements(ele, bd); // 解析 <qualifier> 标签
parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele)); return bd; }catch (ClassNotFoundException ex) {
// 错误输出...
}catch (NoClassDefFoundError err) {
// 错误输出...
}catch (Throwable ex) {
// 错误输出...
}finally {
// 出栈操作
this.parseState.pop();
} return null;
}

2.Beandefinition 注册

在 DefaultBeanDefinitionDocumentReader 关于 bean 标签的注册方法如下:

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  • 1

下面来看 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法。该方法的主要作用是调用注册器完成注册过程。

public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException { // 取得 BeanDefinition 实例的标识
String beanName = definitionHolder.getBeanName(); // 关键 -> 调用注册器实现注册过程
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 取得 BeanDefinition 实例的所有别名
String[] aliases = definitionHolder.getAliases(); if (aliases != null) {
for (String alias : aliases) {
// 往注册器的 aliasMap 添加 alias 的过程
registry.registerAlias(beanName, alias);
}
}
}

08.Spring Bean 解析 - BeanDefinitionDocumentReader的更多相关文章

  1. Spring Bean注册解析(一)

           Spring是通过IoC容器对Bean进行管理的,而Bean的初始化主要分为两个过程:Bean的注册和Bean实例化.Bean的注册主要是指Spring通过读取配置文件获取各个bean的 ...

  2. Spring源码-IOC部分-Xml Bean解析注册过程【3】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  3. spring源码阅读(1)bean解析

    public class Test { public static void main(String[] args) throws Exception { BeanFactory beanFactor ...

  4. Spring ConfigurationClassPostProcessor Bean解析及自注册过程

    一.Bean的自注册过程 二.自注册过程说明 ConfigurationClassParser解析流程  1.处理@PropertySources注解,配置信息的解析 2.处理@ComponentSc ...

  5. Spring Bean注册解析(二)

           在上文Spring Bean注册解析(一)中,我们讲解了Spring在注册Bean之前进行了哪些前期工作,以及Spring是如何存储注册的Bean的,并且详细介绍了Spring是如何解析 ...

  6. 手撸Spring框架,设计与实现资源加载器,从Spring.xml解析和注册Bean对象

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你写的代码,能接的住产品加需求吗? 接,是能接的,接几次也行,哪怕就一个类一片的 i ...

  7. Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  8. Spring Boot 启动源码解析结合Spring Bean生命周期分析

    转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...

  9. Spring配置文件解析--bean属性

    1.bean设置别名,多个别名用逗号隔开 <!--使用alias--> <bean id="app:dataSource" class="...&quo ...

随机推荐

  1. Operating System-进程/线程内部通信-竞争条件(Race Conditions)

    从本文开始介绍进程间的通信,进程间通信遇到的问题以及方式其实和线程之间通信是一致的,所以进程间通信的所有理论知识都可以用在线程上,接下来的系列文章都会以进程之间的通信为模版进行介绍,本文主要内容: 进 ...

  2. 巧用函数实现js插入css样式

    我用的是webstorm,当写css 样式时候,会没有提示,可以按Ctrl+Alt+Space.

  3. 四种生成和解析XML文档的方法介绍

    解析XML的方法越来越多,但主流的方法也就四种,即:DOM.SAX.JDOM和DOM4J 1.DOM(Document Object Model) DOM是用与平台和语言无关的方式表示XML文档的官方 ...

  4. Random简介

    Random类 (java.util) Random类中实现的随机算法是伪随机,也就是有规则的随机.在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要 ...

  5. spring 4.0 注解数据验证2

    在spring 4.0 注解数据验证1中有基本的数据验证方法.还是那个POJO: package com.suyin.pojo; import java.lang.reflect.Field; imp ...

  6. 具体问题:Spring 事务的隔离性,并说说每个隔离性的区别

    使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/b ...

  7. 树莓派 Learning 002 装机后的必要操作 --- 06 共用键鼠 之 windows和树莓派

    树莓派 装机后的必要操作 - 使用Synergy软件 共用键鼠 之 windows和树莓派 我的树莓派型号:Raspberry Pi 2 Model B V1.1 装机系统:NOOBS v1.9.2 ...

  8. ssh远程转发使远程主机在所有ip上监听

    起因:突然一夜之间电信扰拨号ip全变内网地址了,这样即使用了动态域名,绑定的也不是本机ip,外部无法访问了.虽然打电话找电信反映说是可以改回来,但必须先解决眼前的问题,访问内网服务器上的svn仓库. ...

  9. 【机器学习】分类器组合——AdaBoost

    AdaBoost是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这些弱分类器集合起来,构成一个更强的最终分类器(强分类器). AdaBoost其实只是boost的一个特 ...

  10. C#中索引器Indexer的学习使用

    索引器 顾名思义,是用来索引的,那么C#中索引器是用来索引什么的呢 首先我们知道,C#中的数组是本身就可以索引的,那么C#中的类和结构呢,类和结构的实例是无法索引的,如果我们想让C#中类或者结构的实例 ...