spring在注入bean的时候,可以通过bean.xml来配置,在xml文件中配置bean的属性,然后spring在refresh的时候,会去解析xml配置文件,这篇笔记,主要来记录。xml配置文件的解析过程

先把测试的代码贴出来吧

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">
  5.  
  6. <bean id="userBean" class="com.luban.springsource.xml.UserBean"></bean>
  7. </beans>
  8.  
  9. public class XmlBeanTest {
  10. public static void main(String[] args) {
  11. ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
  12. System.out.println(ac.getBean(UserBean.class));
  13. }
  14. }

UserBean就是一个普通的类,没有加任何注解

下面我们来一步一步解析源码

对于Xml格式的,是通过ClassPathXmlApplicationContext来实现的,首先会进入到对应的构造函数中

  1. public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
  2. super(parent);
  3. this.setConfigLocations(configLocations);
  4. if (refresh) {
  5. this.refresh();
  6. }
  7. }

在这段代码中,第三行这里,主要是把当前要解析的xml文件存放到了一个String数组中,在后面解析的时候,会遍历这个数据,挨个解析xml文件

this.refresh()方法完成了解析和初始化,主要来看这个方法

  1. @Override
  2. public void refresh() throws BeansException, IllegalStateException {
  3. synchronized (this.startupShutdownMonitor) {
  4. // Prepare this context for refreshing.
  5. //准备工作包括设置启动时间、是否激活标志位、初始化属性源配置
  6. prepareRefresh();
  7.  
  8. // Tell the subclass to refresh the internal bean factory.
  9. /**
  10. * 返回一个factory
  11. * xml格式的配置文件,是在这个方法中扫描到beanDefinitionMap中的
  12. * 在org.springframework.web.context.support.XmlWebApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)中会创建一个XmlBeanDefinitionReader来解析xml文件
  13. * 会把bean.xml解析成一个InputStream,然后再解析成document格式
  14. * 按照document格式解析,从root节点进行解析,判断root节点是bean?还是beans?还是import等,如果是bean
  15. * 就把解析到的信息包装成beanDefinitionHolder,然后调用DefaultListablebeanFactory的注册方法将bean放到beanDefinitionMap中
  16. */
  17. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  18.  
  19. // Prepare the bean factory for use in this context.
  20. //准备工厂
  21. prepareBeanFactory(beanFactory);
  22.  
  23. try {
  24. // Allows post-processing of the bean factory in context subclasses.
  25. postProcessBeanFactory(beanFactory);
  26.  
  27. // Invoke factory processors registered as beans in the context.
  28. invokeBeanFactoryPostProcessors(beanFactory);
  29.  
  30. // Register bean processors that intercept bean creation.
  31. registerBeanPostProcessors(beanFactory);
  32.  
  33. // Initialize message source for this context.
  34. //初始化MessageSource组件(该组件在spring中用来做国际化、消息绑定、消息解析)
  35. initMessageSource();
  36.  
  37. // Initialize event multicaster for this context.
  38. /**
  39. * 注册一个多事件派发器
  40. * 先从beanFactory获取,如果没有,就创建一个,并将创建的派发器放到beanFactory中
  41. */
  42. initApplicationEventMulticaster();
  43.  
  44. // Initialize other special beans in specific context subclasses.
  45. /**
  46. * 这是一个空方法,在springboot中,如果集成了Tomcat,会在这里new Tomcat()
  47. */
  48. onRefresh();
  49.  
  50. // Check for listener beans and register them.
  51. /**
  52. * 注册所有的事件监听器
  53. * 将容器中的时间监听器添加到 applicationEventMulticaster 中
  54. *
  55. */
  56. registerListeners();
  57.  
  58. // Instantiate all remaining (non-lazy-init) singletons.
  59. /**
  60. * TODO
  61. * 完成对bean的实例化
  62. *
  63. * 主要的功能都在这里面
  64. */
  65. finishBeanFactoryInitialization(beanFactory);
  66.  
  67. // Last step: publish corresponding event.
  68. /**
  69. * 当容器刷新完成之后,发送容器刷新完成事件
  70. * publishEvent(new ContextRefreshedEvent(this));
  71. */
  72. finishRefresh();
  73. }
  74.  
  75. catch (BeansException ex) {
  76. if (logger.isWarnEnabled()) {
  77. logger.warn("Exception encountered during context initialization - " +
  78. "cancelling refresh attempt: " + ex);
  79. }
  80.  
  81. // Destroy already created singletons to avoid dangling resources.
  82. destroyBeans();
  83.  
  84. // Reset 'active' flag.
  85. cancelRefresh(ex);
  86.  
  87. // Propagate exception to caller.
  88. throw ex;
  89. }
  90.  
  91. finally {
  92. // Reset common introspection caches in Spring's core, since we
  93. // might not ever need metadata for singleton beans anymore...
  94. resetCommonCaches();
  95. }
  96. }
  97. }

在前面的spring源码博客中有说到过,spring初始化的一个大致流程是这样的:

1.把所有的要注入的class解析,存到beanDefinitionMap中

2.循环遍历beanDefinitionMap,把每个beanDefinition经过初始化,实例化等生命周期方法的处理,转变为spring容器中存储的bean

3.把bean存到bean的单实例池中

今天要说的,主要是第一步这里,把class解析到beanDefinition中,对于注解版的配置,是在invokeBeanFactoryPostProcessors(beanFactory);中完成,区分了@ComponentScan、ImportSeletor、ImportBeanDefinitionRegistrar、@Bean这么几种情况

那么,xml是另外一种方式,主要是在ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();中,完成了对bean.xml的解析

这个方法,进来之后,首先是刷新beanFactory(),其中,有一个loadBeanDefinitions(beanFactory);方法

这个方法里面的逻辑还是比较简单的,大致说一下:

1.new一个xmlBeanDefinitionReader

2.遍历前面存放的配置文件的数组,依次解析

3.将xml配置文件转换成inputSource,然后生成一个Document对象; Document doc = this.doLoadDocument(inputSource, resource);  这里的这个方法,就是把当前xml配置文件转解析成一个document对象,对于这个方法,

我没有怎么深入研究,姑且我们就认为这是一个黑盒方法

真正开始解析xml,是从这个方法中开始的

  1. public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  2. BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
  3. int countBefore = this.getRegistry().getBeanDefinitionCount();
  4. documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
  5. return this.getRegistry().getBeanDefinitionCount() - countBefore;
  6. }

从这里的registerBeanDefinitions方法开始来解析的


这里是解析root根节点(beans)之后,会获取到当前root下面的子节点,然后依次循环处理子节点的代码;

  1. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  2. if (delegate.nodeNameEquals(ele, "import")) {
  3. this.importBeanDefinitionResource(ele);
  4. } else if (delegate.nodeNameEquals(ele, "alias")) {
  5. this.processAliasRegistration(ele);
  6. } else if (delegate.nodeNameEquals(ele, "bean")) {
  7. this.processBeanDefinition(ele, delegate);
  8. } else if (delegate.nodeNameEquals(ele, "beans")) {
  9. this.doRegisterBeanDefinitions(ele);
  10. }
  11.  
  12. }

从上面说的registerBeanDefinitions到这里的解析方法中间没有几步,不细说了,直接把调用链给出来,可以自己debug再细看一下

我们来说解析的这个方法:

会依次判断beans子节点是哪几个,由于我们只配置了一个<bean,所以,来看处理bean的方法

  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
    bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  2.  
  3. try {
    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
    } catch (BeanDefinitionStoreException var5) {
    this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
    }
  4.  
  5. this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
  6.  
  7. }
    在处理<bean>的时候,会返回一个BeanDefinitionHolder对象,在解析注解版的class的时候,也有好多地方是这样用的,把class解析成BeanDefinitionHolder,然后再把beanDefinitionHolder对应的BeanName,作为BeanDefinitionMapkey,
    holder里面的beanDefinition作为value;这里来着重看parseBeanDefinitionElement()方法,因为经过这个方法之后,已经解析成beanDefinition
  1. @Nullable
    public AbstractBeanDefinition parseBeanDefinitionElement(
    Element ele, String beanName, @Nullable BeanDefinition containingBean) {
  2.  
  3. this.parseState.push(new BeanEntry(beanName));
  4.  
  5. String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
    //获取xml中bean节点配置的class(就是类的全类名)
    className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
    parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }
  6.  
  7. try {
    //根据全类名,通过反射
    AbstractBeanDefinition bd = createBeanDefinition(className, parent);
  8.  
  9. //解析<bean>节点的属性信息,比如:lazy/autowire/scope/destroy-method/init-method等,然后把解析到的属性set到beanDefinition中
    parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    //解析bean的子节点 <description>的信息
    bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
  10.  
  11. parseMetaElements(ele, bd);
    //下面两个方法分别解析<bean>的子节点 lookup-method和replaced-method
    parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
  12.  
  13. //解析bean的构造函数,最后调用的是bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
    parseConstructorArgElements(ele, bd);
    parsePropertyElements(ele, bd);
    parseQualifierElements(ele, bd);
  14.  
  15. bd.setResource(this.readerContext.getResource());
    bd.setSource(extractSource(ele));
  16.  
  17. 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();
    }
  18.  
  19. return null;
    }
  1. 这里面,加了一些注释,不细看每个方法了,其实就是解析每个配置的信息,然后把属性信息setbeanDefinition
  2.  
  3. 总结
    1.首先把入参中待解析的xml文件存到了一个string数组中
    2.依次遍历数组,来解析
    3.解析的时候,会把xml文件解析成document对象,然后首先从根节点<beans>开始解析
    4.解析到<bean>节点的时候,会依次解析bean的属性信息以及bean的子节点信息,把解析到的属性setbeanDefinition
    5.beanDefinitionputbeanDefinitionMap

spring源码学习五 - xml格式配置,如何解析的更多相关文章

  1. [spring源码学习]五-BeanPostProcessor的使用

    一.接口描述 spring提供了一个接口类-BeanPostProcessor,我们叫他:bean的加工器,应该是在bean的实例化过程中对bean做一些包装处理,里边提供两个方法 public in ...

  2. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

  3. Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...

  4. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  5. spring源码学习之路---IOC初探(二)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章当中我没有提及具体的搭 ...

  6. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  7. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

  8. Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

    写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...

  9. 【目录】Spring 源码学习

    [目录]Spring 源码学习 jwfy 关注 2018.01.31 19:57* 字数 896 阅读 152评论 0喜欢 9 用来记录自己学习spring源码的一些心得和体会以及相关功能的实现原理, ...

随机推荐

  1. CVE-2019-17671:Wordpress未授权访问漏洞复现

    0x00 简介 WordPress是一款个人博客系统,并逐步演化成一款内容管理系统软件,它是使用PHP语言和MySQL数据库开发的,用户可以在支持 PHP 和 MySQL数据库的服务器上使用自己的博客 ...

  2. Wininet-Post

    #include "stdafx.h"#include <Windows.h>#include <wininet.h>#include <iostre ...

  3. C++学习笔记11_STL

    STL又叫标准模板库,提供各种容器. STL是C++一部分,不休要额外安装什么,它被内建在编译器之内. STL重要特点是,数据结构和实现分离. *所谓迭代器,类似一个游标,使用++指向下一个元素,使用 ...

  4. NOIP模拟 18

    这次时间分配不合理,沉迷大模拟无法自拔 虽然A了但是根本就没给T3留时间555 T3如果有时间看看数据范围应该可以想到QJ掉20分的555 T1 引子 打这题的时候感觉自己在做图像处理.. 然后调了各 ...

  5. js内容溢出用省略号(...)表示

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. 6.1Hadoop属性Configuration配置API

    6.1  Hadoop属性配置API Hadoop需要添加一些自定义的属性值,可以通过Configuration类的实例来加载xml配置文件中的属性值. (1)   xml配置文件的格式 <?x ...

  7. [ZJOI2013]K大数查询——整体二分

    题目描述 有N个位置,M个操作.操作有两种,每次操作如果是: 1 a b c:表示在第a个位置到第b个位置,每个位置加上一个数c 2 a b c:表示询问从第a个位置到第b个位置,第C大的数是多少. ...

  8. 『题解』洛谷P1083 借教室

    更好的阅读体验 Portal Portal1: Luogu Portal2: LibreOJ Portal3: Vijos Description 在大学期间,经常需要租借教室.大到院系举办活动,小到 ...

  9. win7 安装php插件imagick

        win7 安装php插件imagick  <h2>安装步骤:</h2><h2><a name="t1"></a> ...

  10. vue ui九宫格、底部导航、新闻列表、跨域访问

    一.  九宫格 九宫格:在mint-ui组件库基于vue框架 mui不是基于vue框架 只是css/js文件 (1)官方网站下载安装包 (2)copy css js fonts[字体图标] src/l ...