概述
初始化过程
无非是读取->解析->注册,相信程序员都能实现它,但是要把它变成一个框架性的工具还是需要走很多路的。
load 即资源加载,具体说就是找到合适的工具读取合适的资源(不同的方法读取不同的资源,下一篇有详解)
parse 即资源解析,这里的标准比较多,先是根据读取XML中的关键字“DTD”来选择XML资源的校验模式,不知道是不是我代码读错了,我觉得有点土诶;然后根据相对应的实体解析类(这里没仔细研究,但是可以自定义shema标准去拓展)生成DOM;再托管给documentReader去解析DOM生成一个个BEAN;最后代理给BeanDefinitionParserDelegate去解析一个个的BEAN,生成BeanDefinition(这里其实被包装了一下)。
registor 即注册,首先XMLBeanFactory说代理也好,说回调也好,说托管也好,方式都是把自己传下去了,最然过程中自己变样了,从XMLBeanFactory到BeanDefinitionRegistry到XmlReaderContext,最后回到BeanDefinitionRegistry(注意BeanDefinitionRegistry和XMLBeanFactory的区别,而且XMLBeanFactory就逻辑上而言没有传到BeanDefinitionParserDelegate中,虽然代码中有这样写到,具体原因是因为BeanDefinitionParserDelegate只负责将Bean变成BeanDefinition),最后将上一个阶段解析好的BeanDefinition注册到一个MAP中交给XMLBeanFactory来管理。
类图
该类的核心是XmlBeanDefinition,该类相关的:左面两个类真是SpringExt拓展的类,提供XSD资源加载策略和Bean的解析策略等;右面两个类可以用第三方替换,旨在提供解析DOM的实现;下面那个类主要是依据DOM解析成一个个的BeanDenifition,主要定义了一些默认的行为。
框架中主要体现的类大致分为三类:控制类、环境类和行为类(control、context、parse/handle)
环境类有三种,分别是:BeanDefinitionRegistry,XmlReaderContext和ParserContext,且都持有BeanDefinition的引用
行为类有:BeanDefinitionParserDelegate,BeanDefinitionParser,NamespaceHandler,EntityResolver
控制类有:XmlBeanDefinitionReader,BeanDefinitionDocumentReader,NamespaceHandlerResolver
详解
从BeanFactory来详解初始化过程,毕竟ApplicationContext的初始化只是一个inner BeanFactory,核心也是利用的BeanFactory来实现的。
XmlBeanDefinitionReader
resourceCurrentlyBeingLoaded变量的作用:线程本地的变量保存了已经读取的配置文件,以防循环加载(import会触发)
最后的关闭流之前其实流已经被关闭了,因为框架采用了jaxp的dom方式的xml解析,在最后已经被关闭,因此在拓展的时候要注意这一点。
这个类是用来拓展spring的入口点,回顾前面的类图,分析下该类会发现:该类关联了左面两个类,右面两个类和下面一个类。左面两个是用来定义xsd文件的加载策略和解析策略的(正是x3的精髓所在);右面两个类是用来解析xml的,当然你可以哟过sax的解析方法替换之;下面一个类是把解析完的dom二次解析为一个个的BeanDefinition。其实这五个点你都可以替换。
XmlBeanDefinitionReader
莫要被这个方法简单的几行所蒙蔽,这三行代码都很值得研究,尤其是第二行,哈哈~这个折腾了我好久,其实最后还是没怎么看懂。
首先是判断文档需要采用dtd验证还是xsd验证,具体实现不难,基于字符验证的。
然后采用jaxp的dom方式来解析xml,getEntityResolver()这个入参既是把我们自定义的dtd/xsd文件的加载策略嵌入进去,所以X3框架的xsd可以采用spring包里面自带的,也可以采用webx3-core里面自带的,也可以采用工程meta目录下面的,大家有兴趣的可以去看看。
XmlBeanDefinitionReader
构建dom解析的上下文:xmlReaderContext,因为持有NamespaceHandlerResolver因此就具有解析成BeanDefinition的能力,有个细节注意下,此上下文依旧持有BeanDefinitionRegister的引用,为的就是最后的注册
这个方法是第二个拓展点:NamespaceHandlerResolver的入口点
BeanDefinitionDocumentReader
拓展点很明显,是前置处理器和后置处理器
创建了个代理去解析dom,其实这也是一个control-action的模型,BeanDefinitionDocumentReader处理的是dom树,而代理处理的是每一个节点,因此具体的实现都是在代理类之中。
BeanDefinitionDocumentReader
默认的命名空间和自定义的命名空间的解析方式差距还是挺大的,默认的用不到NamespaceHander,具体的解析实现都在BeanDefinitionParseDelegate中;自定义的命名空间的BeanDefinition的解析过程:NamespaceHandlerResolver解析出具体的NamespaceHandler,然后依据该handler取出具体的BeanDefinitionParser来解析一个个element元素
哪些是默认的命名空间,哪些是自定义的命名空间呢?是不是我们自己写的哪些xsd算是自定义呢?其实仅仅http://www.springframework.org/schema/beans该命名空间是默认命名空间,其他的均为自定义,诸如aop之流都属于自定义范畴,可以去看下spring的jar包中的spring.handler文件(x3框架没有该文件的原因是x3框架只有一个handler)
NamespaceHandler的理解:spring中的涵义为,管理一个命名空间下的所有parsers;但是X3中不一样,X3中自定义很多命名空间,但是只有一个hander,因此x3框架中的涵义可以理解为,管理所有拓展点的parsers。
BeanDefinitionParseDelegate(自定义命名空间的解析)
spring默认的NamespaceHanderResolver会读取meta/spring.handler,依据namespaceUri取得相应的hander
生成解析所需要的上下文:ParserContext(持有BeanDefinitionRegister,解析完要注册的嘛)
X3框架就有很多parsers,都是继承的该类
BeanDefinitionDocumentReader(默认命名空间的解析)
注册方法放在工具类中,因为很多地方会触发该注册行为
装饰不知对应的何功能?
BeanDefinitionParserDelegate
- 为何要包装一下,见注释
BeanDefinitionParserDelegate
- BeanDefinition首次出现在了这个类中,标注下
BeanDefinitionDelegate
- 举个解析bean节点的所有property属性来说明吧
BeanDefinition.parsePropertyValue(…)
解析map的例子
返回的是个map,不知道getBean的时候是怎么处理的,待后续研究
BeanDefinition阶段的属性值的转化是在getBean的时候处理的,这个也值得研究,比如X3框架中ParserRequestContext对传递的参数做类型转换也用到了这个功能点,在学习getBean的时候深入下
涉及到的相关功能点
XML解析
由于时间比较仓促,就概要性的介绍下,主要是自己学习过程中的疑惑
- 有图有真相,先来两张典型的图
XML验证的时候需要一个验证文件,XSD或者DTD,但是需要一个PublicId和SystemId,网上的说法众说纷纭,参照spring的实现,我的理解是这样的:首先,PublicId和SystemId指的都是XML文件的外部引用,只不过前者大家的认知度更高一点,后者更加私有一点;其次,他们表达的都是验证文件的位置,在DTD中如果是PUBLIC的话需要指明详细地址(就是第二个参数),如果是SYSTEM的话就不需要用简称了,但是在XSD中并没有这个概念;最后,在代码中需要传入PublicId和SystemId,对于DTD都应该知道对应的是什么,但是XSD中Public为null,具体的url为SystemId
验证文件填写的网络地址是不是每次验证都需要去下载呢?这就要看EntityResolver了,一般都是优先从本地加载,这是一个策略模式的应用
验证XML都干了哪些事情:验证文档的合法性和填充一些默认的值。
大致是如何解析XML的:主要有两种解析xml的方法,DOM和SAX,前者将xml整个载入内存,然后遍历每个节点,读取对应的验证文件进行验证;后者主要针对于xml过大的情况,它是事件驱动的方式来完成xml的解析,并且是分批次的读入XML来解析,因此存在无法修改的弊端
解析XML和验证XML是如何交互的:首先,解析XML,当发现当前读取节点的命名空间的xsd没有加载的时候,加载该xsd,生成了一个dom语法,然后依据该语法验证该节点,当验证通过之后继续解析。
SpringExt相关
其实和springExt相关的实现已经嵌入到了前面了,这里就简单贴两张图,加深下理解
- 流程图
- 主体实现类
- X3框架是如何嵌入自己实现的XmlApplicationContext