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);
第十,完成刷新过程
Spring之XML解析的更多相关文章
- 这一次搞懂Spring的XML解析原理
前言 Spring已经是我们Java Web开发必不可少的一个框架,其大大简化了我们的开发,提高了开发者的效率.同时,其源码对于开发者来说也是宝藏,从中我们可以学习到非常优秀的设计思想以及优雅的命名规 ...
- Spring的xml解析原理分析【转载】
一:前言 二:spring的配置文件 三:依赖的第三方库.使用技术.代码布局 四:Document实现 五:获取Element的实现 六:解析Element元素 七:Bean创造器 八:Ioc容器的创 ...
- spring AOP XML解析
<aop:config> 标签的解析: <bean id="loggingAspect" class="com.zhuguang.jack.aop.as ...
- 使用JDom解析XML文档模拟Spring的配置文件解析
在J2EE项目中可能会涉及到一些框架的使用,最近接触到了SSH,拿Spring来说配置文件的使用是相当重要的,Spring的配置文件是一个xml文件,Spring是如何读取到配置文件并进行依赖注入的呢 ...
- 源码跟读,Spring是如何解析和加载xml中配置的beans
Spring版本基于: 跟踪代码源码基于: https://github.com/deng-cc/KeepLearning commit id:c009ce47bd19e1faf9e07f12086c ...
- spring+mybaits xml配置解析----转
一.项目中spring+mybaits xml配置解析 一般我们会在datasource.xml中进行如下配置,但是其中每个配置项原理和用途是什么,并不是那么清楚,如果不清楚的话,在使用时候就很有可能 ...
- 曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
随机推荐
- 【python】详解事件驱动event实现
[python]详解事件驱动event实现 所有的计算机程序都可以大致分为两类:脚本型(单次运行)和连续运行型(直到用户主动退出). 1.脚本型:脚本型的程序包括最早的批处理文件以及使用Python做 ...
- powershell命令教程
启动 powershell #字符串操作 对象操作 "hello".Length #进程操作 PS C:\> notepad PS C:\> $process=get- ...
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_24-页面预览-页面预览测试
通过nginx转发到预览的地址 重启nginx 添加页面预览按钮 调整下列表的列的宽度
- 找回Firefox4的状态栏!Status-4-Evar扩展
Status-4-Evar这个扩展能让Firefox4故意移除的状态栏给找回来!官方下载地址为:https://addons.mozilla.org/zh-CN/firefox/addon/23528 ...
- java中如何在键盘中输入一串数字然后存入数组中?
import java.util.Scanner; public class Tset { public static void main(String[] args) { System.out.pr ...
- ES6 var,const , let三者区别
每天学一点,知识涨一张 var 默认是会变量提升的,变量可以修改: let 定义变量,变量可以修: const 定义必须有常量值,const的值一但写上不可更改:let 与const相同之处:1> ...
- elasticsearch 的post put 方式的对比 setting mapping设置 - 添加查询数据
1.POST和PUT都可以用于创建 2.PUT是幂等方法,POST不是.所以post用户更新,put用于新增比较合适. 参考:https://yq.aliyun.com/articles/366099 ...
- python初学第一节课
### python的基础 推荐学习python的一些资料, 首先就是书籍<Python从入门到实践>, 这本书评分很高, 主要是对Python讲解的比较清楚, 后面还有具体的实战项目 推 ...
- [Cometoj#3 D]可爱的菜菜子_线段树_差分_线性基
可爱的菜菜子 题目链接:https://cometoj.com/contest/38/problem/D?problem_id=1543 数据范围:略. 题解: 首先,如果第一个操作是单点修改,我们就 ...
- #【Python】【基础知识】【模块】【Python的常用模块】
在IDE解释器中,执行help('modules')可查看当前环境可调用的Python模块: >>> help('modules') Please wait a moment whi ...