基本概念

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

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

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

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

  1. public interface BeanDefinitionDocumentReader {
  2. void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)throws BeanDefinitionStoreException;
  3. }

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


源码分析

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

首先来看该方法的入参:

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

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

再来看它的具体流程:

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

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

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

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

  1. protected void doRegisterBeanDefinitions(Element root) {
  2. // 创建 delegate 对象
  3. BeanDefinitionParserDelegate parent = this.delegate;
  4. this.delegate = createDelegate(getReaderContext(), root, parent);
  5. // 验证 XML 文件的命名空间,即判断是否含有 xmlns="http://www.springframework.org/schema/beans"
  6. if (this.delegate.isDefaultNamespace(root)) {
  7. // 解析 profile
  8. String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
  9. if (StringUtils.hasText(profileSpec)) {
  10. String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
  11. BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  12. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
  13. return;
  14. }
  15. }
  16. }
  17. // 空方法
  18. preProcessXml(root);
  19. // 关键 -> 开始解析 Bean 定义
  20. parseBeanDefinitions(root, this.delegate);
  21. // 空方法
  22. postProcessXml(root);
  23. this.delegate = parent;
  24. }

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

  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  2. // 验证 XML 文件的命名空间
  3. if (delegate.isDefaultNamespace(root)) {
  4. // 取得 <beans> 的所有子节点
  5. NodeList nl = root.getChildNodes();
  6. // 遍历 <beans> 的子节点
  7. for (int i = 0; i < nl.getLength(); i++) {
  8. Node node = nl.item(i);
  9. // 判断节点是不是 Element 类型
  10. if (node instanceof Element) {
  11. Element ele = (Element) node;
  12. if (delegate.isDefaultNamespace(ele)) {
  13. // 关键 -> 解析 <beans> 子节点的内容
  14. parseDefaultElement(ele, delegate);
  15. }else {
  16. // 解析自定义元素,暂不探究
  17. delegate.parseCustomElement(ele);
  18. }
  19. }
  20. }
  21. }else {
  22. delegate.parseCustomElement(root);
  23. }
  24. }

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

  1. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 处理 <import> 标签,将资源进行合并再统一解析
  3. if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
  4. importBeanDefinitionResource(ele);
  5. }
  6. // 处理 <alias> 标签
  7. else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
  8. processAliasRegistration(ele);
  9. }
  10. // 关键-> 处理 <bean> 标签
  11. else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
  12. processBeanDefinition(ele, delegate);
  13. }
  14. // 处理 <beans> 标签,回到开始解析 document 的地方
  15. else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
  16. // recurse
  17. doRegisterBeanDefinitions(ele);
  18. }
  19. }

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

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

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

  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 关键 -> ①交给委托类 delegate 来完成解析过程 ,并返回 BeanDefinitionHolder 对象
  3. // 该对象存储了 BeanDefinition 的基本信息
  4. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  5. // 交给委托类 delegate 来完成修饰过程,这里暂不探究
  6. if (bdHolder != null) {
  7. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  8. try {
  9. // 关键 -> ②注册最后的装饰实例
  10. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  11. }catch (BeanDefinitionStoreException ex) {
  12. //错误输出...
  13. }
  14. // Send registration event.
  15. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  16. }
  17. }

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


1.BeanDefinition 解析

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

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

下面来看下 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement 方法。

  1. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
  2. return parseBeanDefinitionElement(ele, null);
  3. }
  4. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
  5. // 取得 <bean> 标签的 id 属性
  6. String id = ele.getAttribute(ID_ATTRIBUTE);
  7. // 取得 <bean> 标签的 name 属性
  8. String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
  9. // 创建 List 用于存方法 alsas(别名)集合
  10. List<String> aliases = new ArrayList<String>();
  11. if (StringUtils.hasLength(nameAttr)) {
  12. // 将 nameArr 按照(,)或(;)分割成数组
  13. String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  14. // 添加进行别名集合
  15. aliases.addAll(Arrays.asList(nameArr));
  16. }
  17. // 判断 id 是否为空,若为空则取别名集合的第一个元素当作 id ,并将其从别名集合当中移除
  18. String beanName = id;
  19. if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
  20. beanName = aliases.remove(0);
  21. // 日志输出...
  22. }
  23. // 检查 id(标识)和 alias(名别)是否唯一
  24. if (containingBean == null) {
  25. checkNameUniqueness(beanName, aliases, ele);
  26. }
  27. // 关键 -> 解析 <bean> 标签的 class 属性 以及相关特性标签,并将其添加进 beanDefinition 返回
  28. AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
  29. if (beanDefinition != null) {
  30. // 判断是否存在 beanName(id)
  31. if (!StringUtils.hasText(beanName)) {
  32. try {
  33. if (containingBean != null) {
  34. beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
  35. }else {
  36. // 由 Spring 自动生成 beanName(id)
  37. beanName = this.readerContext.generateBeanName(beanDefinition);
  38. // 取得 bean 的 完整类名可用
  39. String beanClassName = beanDefinition.getBeanClassName();
  40. // 将 beanName 添加进 alais 集合
  41. if (beanClassName != null &&
  42. beanName.startsWith(beanClassName) &&
  43. beanName.length() > beanClassName.length() &&
  44. !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
  45. aliases.add(beanClassName);
  46. }
  47. }
  48. // 日志输出...
  49. }catch (Exception ex) {
  50. error(ex.getMessage(), ele);
  51. return null;
  52. }
  53. }
  54. // 将 aliases 集合数组化
  55. String[] aliasesArray = StringUtils.toStringArray(aliases);
  56. // 关键 -> 返回一个 BeanDefinitionHolder 实例,用于存储信息
  57. return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
  58. }
  59. return null;
  60. }

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

  1. public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {
  2. // 入栈操作,往 parseState 中添加一个 新建的 BeanEntry
  3. this.parseState.push(new BeanEntry(beanName));
  4. // 取得 <bean> 标签的 class 属性
  5. String className = null;
  6. if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
  7. className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
  8. }
  9. try {
  10. // 取得 <bean> 标签的 parent 属性
  11. String parent = null;
  12. if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
  13. parent = ele.getAttribute(PARENT_ATTRIBUTE);
  14. }
  15. // 根据 class,parent 的属性值创建一个 BeanDefinition
  16. AbstractBeanDefinition bd = createBeanDefinition(className, parent);
  17. // 取得 <bean> 标签的其他特性属性,并添加进 BeanDefinition 。如:
  18. // scope、
  19. // lazy-init
  20. // autowire
  21. // primary、autowire-candidate
  22. // depends-on、dependency-check
  23. // init-method、destroy-method
  24. // factory-method、factory-bean
  25. parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
  26. bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
  27. // 解析 <mate> 标签
  28. parseMetaElements(ele, bd);
  29. // 解析 <lookup-method> 标签
  30. parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
  31. // 解析 <replaced-method> 标签
  32. parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
  33. // 解析 <constructor-arg> 标签
  34. parseConstructorArgElements(ele, bd);
  35. // 解析 <property> 标签
  36. parsePropertyElements(ele, bd);
  37. // 解析 <qualifier> 标签
  38. parseQualifierElements(ele, bd);
  39. bd.setResource(this.readerContext.getResource());
  40. bd.setSource(extractSource(ele));
  41. return bd;
  42. }catch (ClassNotFoundException ex) {
  43. // 错误输出...
  44. }catch (NoClassDefFoundError err) {
  45. // 错误输出...
  46. }catch (Throwable ex) {
  47. // 错误输出...
  48. }finally {
  49. // 出栈操作
  50. this.parseState.pop();
  51. }
  52. return null;
  53. }

2.Beandefinition 注册

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

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

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

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

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. Mybatis新版实践

    配置文件节点顺序 MyBatis的configuration节点需要有顺序,首先是propertes然后是settings,environment... @Param注解参数 对于Mapper接口,如 ...

  2. hdu 4372 Count the Buildings —— 思路+第一类斯特林数

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=4372 首先,最高的会被看见: 然后考虑剩下 \( x+y-2 \) 个被看见的,每个带了一群被它挡住的楼, ...

  3. File:isctype.c Line 68

    刚接触DSP,拿来别人的代码,编译时,发现如下错误: 百思不得琪姐,一番调查之后,发现自己的工程worksapce中有中文路径,怎一个fuck了得.

  4. JVM插庄之一:JVM字节码增强技术介绍及入门示例

    字节码增强技术:AOP技术其实就是字节码增强技术,JVM提供的动态代理追根究底也是字节码增强技术. 目的:在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修 ...

  5. 用paramiko写堡垒机

    paramiko paramiko模块,基于SSH用于连接远程服务器并执行相关操作. 基本用法 SSHClient 基于用户名密码连接: 基础用法: import paramiko # 创建SSH对象 ...

  6. 调试opencv调用摄像头程序时碰到的问题

    昨天晚上想把opencv学习笔记整理一下,当跑opencv调用摄像头的程序的时候老是出现Assertion failed (size.width>0 && size.height ...

  7. 洛谷-机器翻译-NOIP2010提高组复赛

    题目背景 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章. 题目描述 这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换.对于每个英文单词,软件会先 ...

  8. mkfs在特定的分区上建立 linux 文件系统

    Linux mkfs命令用于在特定的分区上建立 linux 文件系统 使用方式 : mkfs [-V] [-t fstype] [fs-options] filesys [blocks]参数 :    ...

  9. 9、samtool view

    参考:https://www.sogou.com/link?url=DOb0bgH2eKh1ibpaMGjuy6YnbQPc3cuKbWqIy1k6SBFomuBEhdSpHkUUZED5fr2OTk ...

  10. 微信小程序自学第二课:app及页面的生命周期、使用setData绑定数据

    一.App声明周期 1.App() app.js中的App() 函数用来注册一个小程序.接受一个 object 参数,其指定小程序的生命周期函数等. 示例代码: App({ onLaunch: fun ...