Spring之XML解析

XML解析,我们可以通过我们常用的以下代码作为入口


也许,我们习惯使用第一种加载方式,但是以前也存在 第二种加载,并且这两种加载也有差别,下面再来分析。


先分析 第二种 使用 BeanFactory 加载方式

进入到  XMLBeanFactory中看到


我们到  super(parentBeanFactory);  这个方法中,可以看到有一个操作,  ignoreDependencyInterface(.....class)操作


那么 这三个方式是干什么的呢。字面意思来分析是忽略依赖接口,有什么用呢,这里举个例子:当我们有两个类A和B,A中有个属性依赖B,那么在实例化A时,需要B已经实例化,如果没有实例化那么就会开始实例化B,然后在注入到A中,但是如果B在这些忽略依赖接口的集合中的话,此时B就不会实例化。所以一般这些类都是Spring用来特殊处理用的。

跟着主线,再来解析下面的一句  this.reader.loadBeanDefinitions(resource);


在这里  将Resource对象在封装一下成 EncodeResource对象,在这里可以设置字符集和编码格式,默认为null

下面进入 核心  loadBeanDefinitions方法中


第一步,是将 Resource对象的输入流封装成InputSource对象中的 ByteStream中,以及设置编码。

第二步,是真正开始解析 XML了,下面让我们进入 到 doLoadBeanDefinitions中吧。


该方法中,涉及到了 两步

第一步,读取流,使用dom4j解析xml,封装成Document对象

第二步,解析xml,注册BeanDefinition对象到工厂中。

现在开始 解析 第一步,进入到  doLoadDocument方法中


在加载xml之前,有一步骤很重要,那就是判断该xml是基于 xsd 还是 dtd 方式,进入到 getValidationModeForResource方法中可以看到


默认的话 通过 getValidationMode()的结果就是 VALIDATION_AUTO,所以进入到我划线方法中



在这个方法中,我们可以看到 如何判断 XML验证是 DTD还是XSD的,说白了就是判断是否是 DOCTYPE 开头的,如果是,那么就用 DTD方式 否则就是 XSD。

当我们获取到了 资源验证模式后,怎个根据不同验证方式来验证呢,我们来分析 getEntityResolver()方法


那么EntityResolver这个类作用是什么呢。通过一下例子讲讲


假如是以上声明,我们可以知道不是 <!DOCTYPE>开头,应该使用XSD解析,解析的时候可以获得两个参数 publicId和SystemId,分别是

publicId: null

SystemId: http://www.springframework.org/schema/beans/spring-beans.xsd


假如是以上的声明,应该使用DTD解析,解析的时候可以获取两个参数分别是

publicId: -//Spring//DTD BEAN 2.0//EN

systemId:http://www.springframework.org/dtd/spring-beans-2.0.dtd

我们进入到DelegatingEntityResolver类中查看 resolveEntity()方法


根据systemId的文件后缀来分别使用不同解析器来解析,

如果是dtd解析,则去当前目录下的去找


比如在源码中就存在下面



如果是XSD,则会调用  PluggableSchemaResolver类的 resolveEntity()方法来解析,我们可以看到解析过程如下


首先看这个方法前,要先看看 这个类有个构造方法如下


注意这里的路径,然后我们去看 getSchemaMappings()方法


这个方法将 读取 上面的那个路径 META-INF/spring.schemas 下的内容封装成 Properties,然后根据systemId当作key,来返回xsd文件的路径,这种设计真的很精妙,避免的每次都是通过网络来远程访问,避免网络容易中断带来的文件找不到的影响,我们可以看看 spring.schemas文件的内容,都是通过systemId来返回一个路径,这个路径也在resource目录下,可以看到




自此,xml的验证就到这里。


回到主线,开始加载xml  ,进入到 loadDocument方法中


这里用的是 jdk的 dom解析,想深究的就自己看看,这里我们将xml文件解析成了 Document对象,下面就开始解析Document中的每个元素了。

回到主线,进入 registerBeanDefinitions方法中


这个方法中主要做了以下几件事

第一,创建了一个 文档阅读器

第二,解析之前,记录已存在的 BeanDefinition个数

第三,开始解析,注册BeanDefinition

第四,获取最新的BeanDefinition个数,减去之前记录的,就是本次加载xml注册的BeanDefinition的个数了

下面,进入 registerBeanDefinitions方法中


这里首先获取跟节点,也就是  beans节点,然后继续 看下去


在这个方法中,分析下

第一,创建 BeanDefinitionParserDelegate对象,在 createDelegate()方法中,先创建了对象,然后初始化,那么初始化什么内容呢,起始就是初始化一些 beans标签的一些属性封装到  BeanDefinitionParseDelegate对象中,我们可以去看看


第二,判断是否有 profile属性,这个属性就是为了区别测试和开发环境等类似用法,有兴趣研究的可以看看

第三,开始解析 beanDefinitions了,在其上和下有个两个方法,但是都是空实现,可能是为了以后扩展准备。

下面跟着进入  parseBeanDefinitions()方法中


该方法将获取 beans下的所有元素,遍历,判断是否是默认标签元素,默认标签元素有:import、alias、bean、beans等,如果是默认元素,则调用 parseDefaultElement()方法,否则调用自定义元素的解析方法即 parseCustomElement()。下面对默认元素解析以 bean元素解析作为解析,其他大同小异

进入到 parseDefaultElement方法中


针对bean标签解析,进入 processBeanDefinition中


该方法主线有两个方法

第一个,解析bean标签将属性和参数封装成 BeanDefinitionHolder对象

第二个,注册到BeanDefinitionRegistry中

下面先进入 parseBeanDefinitionElement方法中



该方法比较长,大致写下实现了什么

第一,获取id和name属性,如果存在id,则 beanName = id,否则没有id只有name的话,就用beanName=别名中的第一个。

第二,验证 beanName是否重复

第三,解析 bean标签内容,包括属性,以及bean标签内的例如 propertiey、list、set、map、构造方法等元素的解析都封装到beanDefinition中

第四,如果该bean没有定义id和name,那么根据spring的规则来自动生成beanName

第五,封装成 BeanDefinitionHolder中

由于解析 篇幅太长,这里就写下主线和思路,各位大佬想更多了解可以输入到 parseBeanDefinitionElement()方法中查看。


回到主线再来看看 registerBeanDefinition()方法


这里就是将 beanDefinition注册到 registry中,以beanName为key存储。

自此,bean标签就解析成了 be'anDefinition对象注册到了  registry中,bean标签解析结束,下面写下自定义元素解析。


回到这主线来,进入该方法中


可以看到虽然这方法看起来很简单,但是每行代码都很关键,分析下有如下步骤

第一,获取当前标签的对应的 NameSpaceUri,什么意思呢,比如如下解析 context标签时,获取 uri


第二,根据这个Uri获取解析类,这里也很有意思,和获取XSD验证方式类似,进入 resolve方法


在看这个方法前,同样看看该类的构造


看起来是不是和  上面的  META-INF/spring.schemas很像,对 思路大致一样

同样的在 getHandlerMappings()中会读取 META-INF/spring.handlers 文件,然后根据NameSpaceUri为key获取解析类路径,我们去看看  spring.handlers文件内容,以 <context:component-scan base-package="xx"></context:component-scan>为例,我们解析 context标签,就会去 context工程下得 META-INF下找 spring.schemas文件,文件内容如下:


然后通过 context标签对应的uri找到 ContextNamespaceHandler类,这个类中只有一个方法 init(),那么该方法什么时候调用呢,我们继续看,下面的代码中调用了  init()方法,所以 我们看 ContextNamespaceHandler类中的 init()方法。


在这个方法中,注册了多个解析器,可以看到,这些key跟我们的熟悉的标签一致。

回到主线,

第三,调用handler的parse方法,进入该方法,


刚才我们通过init方法注入了多个解析器,但是 对于 context:component-scan这个标签有自己的解析器,怎么找到这个解析器呢,就通过以上方法,根据 getLocalName方法获取到  component-scan,然后根据这个key找到 解析器


然后 调用该解析器的 parse方法来解析,至于 对于 这个标签的解析设计到  Annotation注解的注入,我们下个文章讲解,这里主要是说下怎么找到对应的解析器。

那么  对于 第二种加载方式 的解析 就到这里,总结一下,其实只有一个方法:loadBeanDefinitions();


下面来介绍下 第一种  也是最常用的 加载方式 使用 ApplicationContext,下面我们进入到 ClassPathXmlApplicationContext类中看看


这里主要的就是  refresh()方法,进入看看


以上方法大致分析如下:

第一,初始化前的准备工作,例如对系统变量或环境变量进行准备和验证

    我们进入到 prepareRefresh()方法中


这两个方法看起来没有什么实现,应该这是一个spring的扩展,这里主要是验证环境变量是否存在,我们可以自定义一个ApplicationContext,集成ApplicationContext,重写 initPropertySources()方法,在其中我们可以要求必须存在某个环境变量,然后下一步验证的时候如果缺少某个环境变量时暴露异常。举个例子


在这个方法里要求必须验证 存在 VAR这个系统变量,如果不存在,则会在   validateRequiredProperties()方法中提示异常。


第二,初始化BeanFactory,加载xml文件解析注册到registry中

    我们进入obtainFreshBeanFactory()方法中看看


这里主要是 刷新bean工厂,进入该方法中可以看到我们熟悉的一幕


在这里开始加载 xml,解析bean


下面的步骤和上面解析xml步骤一致,就不多说了,经过这步后,已经将baen注册到工厂中


第三,xml解析完成,下面就是ApplicationContext中对Spring扩展的支持。

    进入到 prepareBeanFactory()方法中


这些扩展在这里不细写了,想了解的可以研究下

第四,激活各种beanFactory处理器

第五,注册拦截bean创建的bean处理器,这里只是注册

第六,为上下文初始化Message源,即国际化处理

第七,初始化应用消息广播器

第八,在所有bean中,找到Listener bean,并将其注册到 消息广播器中

第九,初始化所有非懒加载的bean

    我们进入到 finishBeanFactoryInitialization()方法中,核心方法是  beanFactory.preInstantiateSingletons(); 这句代码,这句代码是开始初始化单例bean的入口,我们进去看看


这里可以分析下,步骤如下

一,获取所有beanName,循环,然后根据beanName获得对应的bean

二,判断bean是否是抽象的、单例的、非懒加载的,如果满足这三个条件,则提供初始化工作

三,满足初始化条件的情况下,还判断了是否是 FactoryBean类型,如果是FactoryBean类型,如果要获取 FactoryBean对象的话需要在 beanName前面加上 “&”,为什么要这么做,可以去看看有关博客---即 BeanFactory和FactoryBean的区别

四,获取bean,这一步是核心,我们进去看看




这里的步骤有很多,大致分析下

(1)转化beanName,这是什么意思呢,可以通过下图源码中看到如果是 以 “&”开头的要截取掉,这就是当如果我们的bean是FactoryBean类型的bean的情况下做得准备。前面也说了如果是FactoryBean类型的bean获取时需要添加 &。


(2)尝试从缓存中以及singletonFactories中获取 bean对象,我们进入 getSingleton()方法中看看


可以很清楚的看到 显示在 单例的缓存中尝试获取,获取不到就可能在FactoryBean的缓存中,再次获取,如果获取到了通过getObject()方法获取对象,获取不到,则返回null

(3)继续看,如果从缓存中获取到了bean,此时的bean还是最原始的bean,需要通过getObjectForBeanInstance()来解析,这方法中包含了多个步骤,主要还是为了 FactoryBean类型的bean类型验证以及解析工作,获取真正我们需要的对象,这里就不细看了,不是很关键

(4)如果在缓存中不存在bean,所以是第一次创建,那么,首先会判断是否存在 循环依赖问题,那么什么是循环依赖呢,简单介绍下,有两个类A和B,如果A中有个属性是B,B中有属性为A,此时相互依赖,当实例化A时,发现存在属性B,此时又需要先实例化B,转到实例化B时,发现有属性A,又转到需要实例化A,这样就会形成一个循环,无法完成实例化任何一个,此时就会抛出异常。这就是 isPrototypeCurrentlyInCreation()方法的作用

(5)spring实例化bean前有个特点,也是为了解决循环依赖的,那就是在实例化bean前会提前将该对象暴露,添加到一个缓存中,即即将实例化的map中。这就是 markBeanAsCreated()方法的作用

(6)下面就是获取当前要实例化的bean的所有依赖,然后递归创建依赖的bean

(7)最后,判断是否是单例的,还是prototype的,或者看scope,这里主要介绍单例的bean创建过程


我们进入到 getSingleton()方法中,可以看到


在这个方法里,其实就在创建的之前记录一下,在beforeSingletonCreation()方法中体现,然后就是调用 singletonFacetory的回调方法 getObject(),来创建bean,我们可以去看到该方法中只有一个createBean()方法



上图中圈了四个方法,分别介绍作用

(1)Class<?> resolvedClass = resolveBeanClass(mbd, beanName);

    该方法主要是根据beanName获取对应bean的Class对象

(2)mbdToUse.prepareMethodOverrides();

    该方法主要是针对哪些在spring配置中存在lookup-method和replace-method等配置,这两个配置的加载起始就是将配置同意存放在BeanDefiniton中的methodOverrides属性里,而这个函数也是为了针对这两个配置的

(3)Object bean = resolveBeforeInstantiation(beanName, mbdToUse);

    这方法主要针对aop存在的,有关aop后面的文章中会介绍到,这里主要说下,我们在创建bean的时候可能不是获取真实的实例的,可能通过aop方式,创建基于jdk或cglib代理的代理对象,这个方法就是如果通过aop创建了一个队里对象的话,就不会继续执行下去,直接返回代理对象,否则继续执行第四步

(4)Object beanInstance = doCreateBean(beanName, mbdToUse, args);

第十,完成刷新过程




  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值