基本概念

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. yeoman,grunt,bower

    Yeoman主要有三部分组成:yo(脚手架工具).grunt(构建工具).bower(包管理器).这三个工具是分别独立开发的,但是需要配合使用,来实现我们高效的工作流模式. http://www.cn ...

  2. [转]阮一峰:理解RESTful架构

    作者: 阮一峰 日期: 2011年9月12日 越来越多的人开始意识到,网站即软件,而且是一种新型的软件. 这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通 ...

  3. poj 2000 Gold Coins(水题)

    一.Description The king pays his loyal knight in gold coins. On the first day of his service, the kni ...

  4. 【转】 Pro Android学习笔记(四一):Fragment(6):数据保留

    目录(?)[-] 通过fragment参数实现数据保留 对TitleFragment进行修改 对DetailActivity进行修改 通过savedInstanceState进行数据保留 保留frag ...

  5. web攻击之八:溢出攻击(nginx服务器防sql注入/溢出攻击/spam及禁User-agents)

    一.什么是溢出攻击 首先, 溢出,通俗的讲就是意外数据的重新写入,就像装满了水的水桶,继续装水就会溢出,而溢出攻击就是,攻击者可以控制溢出的代码,如果程序的对象是内核级别的,如dll.sys文件等,就 ...

  6. AD9各种布线总结

    1.常规布线:不详细说了,是个人就知道怎么弄.需要说明的是在布线过程中,可按小键盘的*键或大键盘的数字2键添加一个过孔:按L键可以切换布线层:按数字3可设定最小线宽.典型线宽.最大线宽的值进行切换. ...

  7. java继承示例

    package day07; class Fu { int num = 5; } class Zi extends Fu { int num =7; void show() { int num =9; ...

  8. CCNet说明文档

    1.CCNet安装步骤 1)    安装CCNet服务器端:CruiseControl.NET-1.8.5.0-Setup.exe 2)    安装CCNet客户端:CruiseControl.NET ...

  9. hive查询ncdc天气数据

    使用hive查询ncdc天气数据 在hive中将ncdc天气数据导入,然后执行查询shell,可以让hive自动生成mapredjob,快速去的想要的数据结果. 1. 在hive中创建ncdc表,这个 ...

  10. JAVA基础知识总结4(面向对象特征之一:封装)

    封 装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式. 好处:将变化隔离:便于使用:提高重用性:安全性. 封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问. th ...