Dubbo系列笔记之XML配置文件解析流程
简单叨叨一下Dubbo是如何自定义标签给spring承载bean的。
Spring通过XML解析程序将其解析为DOM树,通过NamespaceHandler指定对应的Namespace的BeanDefinitionParser将其转换成BeanDefinition。再通过Spring自身的功能对BeanDefinition实例化对象。Dubbo做的只是实现了NamespaceHandler解析成BeanDefinition。
好了,总结起来就这么简单,下面我们具体来看一下。
一、约束文件schema
下面是一个标准的文件头的格式
Dubbo系列笔记之XML配置文件解析流程
首先自定义的标签会有一些约束规范,比如我自定义的有哪几种标签,标签里面有哪些属性等等,在XML中每个命名空间都会有一个.xsd的约束文件。
Dubbo系列笔记之XML配置文件解析流程
一个约束文件.xsd长得像下面这样
Dubbo系列笔记之XML配置文件解析流程
里面限制自定义的标签里面有哪些属性,属性的类型是什么啊这种。
二、spring.handlers和spring.schemas
当spring解析xml时遇到自定义的标签时,会调用BeanDefinitionParserDelegate.parseCustomElement(...) 方法,如下:
Dubbo系列笔记之XML配置文件解析流程
可以看到接下来通过 DefaultNamespaceHandlerResolver.resolve(String namespaceUri)获得对应的Dubbo处理器。这个namespaceUri就是bubbo的命名空间uri,spring会去查找名字为spring.handlers的文件,里边配置了命名空间对应的handler,如下面:
spring会加载spring.handlers和spring.schemas这两个文件,两个文件长下面这样
代码语言:javascript复制❀ spring.schemas:里面指定了该标签的约束文件本地路径,在解析XML文件时将XSD重定向到本地文件,避免在解析XML文件时需要上网下载XSD文件。通过实现org.xml.sax.EntityResolver接口来实现该功能。
Dubbo系列笔记之XML配置文件解析流程
代码语言:javascript复制❀ spring.handlers:里面指定了由那个handler去处理这些自定义的标签,实现一个handler需要实现org.springframework.beans.factory.xml.NamespaceHandler接口,或使用org.springframework.beans.factory.xml.NamespaceHandlerSupport的子类。
Dubbo系列笔记之XML配置文件解析流程
三、DubboNamespaceHandler
在spring加载完spring.handlers后就知道要通过DubboNamespaceHandler去解析dubbo:xxx这种dubbo的标签。
下面我们来看一下,它长这个样子
Dubbo系列笔记之XML配置文件解析流程
通过上面代码我们可以知道,解析标签的工作并不是namespaceHandler去做的,它做的只是为每个标签注册BeanDefinitionParser,告诉spring哪个BeanDefinitionParser去真正处理这个标签。
四、DubboBeanDefinitionParser
下面我们简单来看一下DubboBeanDefinitionParser长什么样,嗯,大概就下面这个样子吧
Dubbo系列笔记之XML配置文件解析流程
在解析标签时spring会调用BeanDefinitionParser的parse()方法去生成一个BeanDefinition。
我们注意到,parse()方法有两个参数Element element和ParserContext parserContext,element是xml解析器在解析完xml标签后将其组装成一个这样的对象,而第二个参数parserContext,我们通过他的getRegistry()方法获取BeanDefinitionRegistry对象。他长下面这个样
Dubbo系列笔记之XML配置文件解析流程
说到这里,那么我们解析配置的初衷是什么呢?
没错,我们为了把配置解析成bean去交给spring托管。
而BeanDefinitionRegistry的作用主要是向spring注册表中注册 BeanDefinition 实例,通过调用其registerBeanDefinition()方法完成注册的过程。
简单看一下BeanDefinitionRegistry长什么样子
Dubbo系列笔记之XML配置文件解析流程
可以看到BeanDefinitionRegistry是一个接口,它定义了一些注册BeanDefinition的一些必要方法。
那么具体BeanDefinitionRegistry是如何注册bean的呢?
我们看一下它的registerBeanDefinition(String var1, BeanDefinition var2),第一个参数是bean的id,第二个就是BeanDefinition,BeanDefinition描述了一个bean的画像,他是基础的bean定义接口。由他衍生出AbstractBeanDefinition和RootBeanDefinition。
五、BeanDefinition
- AbstractBeanDefinition
他长得挺长的,主要是在BeanDefinition的基础上定义了一些属性,基本囊括了Bean实例化需要的所有信息。如下,
Dubbo系列笔记之XML配置文件解析流程
总体来看这个抽象类长了这些东西:
- Bean的描述信息(例如是否是抽象类、是否单例)
- depends-on属性(String类型,不是Class类型)
- 自动装配的相关信息
- init函数、destroy函数的名字(String类型)
- 工厂方法名、工厂类名(String类型,不是Class类型)
- 构造函数形参的值
- 被IOC容器覆盖的方法
- Bean的属性以及对应的值(在初始化后会进行填充)
- RootBeanDefinition
从spring2.5开始,spring一开始都是使用GenericBeanDefinition类保存Bean的相关信息,在需要时,在将其转换为其他的BeanDefinition类型。
这里两个BeanDefinition可以说是互补的关系,
Dubbo系列笔记之XML配置文件解析流程
我们可以在源码中看到,RootBeanDefinition继承了AbstractBeanDefinition,在其基础上面定义了更多属性。从上图可以看到第一个属性BeanDefinitionHolder,那么这个BeanDefinitionHolder是什么东西呢,
Dubbo系列笔记之XML配置文件解析流程
它保存了bean的名字、别名、以及BeanDefinition持有的bean的一些基础信息。
总结一下:
- 定义了id、别名与Bean的对应关系(BeanDefinitionHolder)
- Bean的注解(AnnotatedElement)
- 具体的工厂方法(Class类型),包括工厂方法的返回类型,工厂方法的Method对象
- 构造函数、构造函数形参类型
- Bean的class对象
那么我这里我们就大概了解了一些BeanDefinition里面有什么东西,那回到上面问题,bean具体是怎么注册的呢?
六、BeanDefinitionRegistry
在上面我们简单看了一下BeanDefinitionRegistry这个接口中有一些方法。下面我们来着重看一下注册方法是怎么实现的 void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException; 。点开这个方法的实现,我们可以看到spring中它有三个实现,如下图,
Dubbo系列笔记之XML配置文件解析流程
我们分别看一下这三个类怎么实现的:
- DefaultListableBeanFactory
Dubbo系列笔记之XML配置文件解析流程
首先,是做了一下校验,判断,看要注册的bean是否已经存在了等等。注意到它有两个关键的成员变量:
Dubbo系列笔记之XML配置文件解析流程
其中beanDefinitionMap这个ConcurrentHashMap注册表,而beanDefinitionNames显而易见是beanName的集合。
观察到,注册bean的最重要步骤就是 this.beanDefinitionMap.put(beanName, beanDefinition); 。
- GenericApplicationContext
再来看一下BeanDefinitionRegistry的第二个实现类GenericApplicationContext:
Dubbo系列笔记之XML配置文件解析流程
注册bean的代码实现只有一行,可以看到他只是调用了上面DefaultListableBeanFactory的注册方法。
- SimpleBeanDefinitionRegistry
最后一个,SimpleBeanDefinitionRegistry。它是这样的一个东西:
Dubbo系列笔记之XML配置文件解析流程
比较关键的一句就是红框所示。
这样下来,我们知道,注册bean最关键的就是往注册表的ConcurrentHashMap中put进去bean的name和BeanDefinition。
七、bean的实例化
到这里为止,我们由Dubbo的xml配置文件解析,延伸到了spring如何注册bean。那么纵观bean的整个生命周期,bean的初始化可以说是为bean的一生埋下了种子,那么最后我们再看看bean初始化的另一个动作---bean注册后是如何被实例化的。附一张bean生命周期全图,顺便看看bean宝宝长大了都要做一些什么:
Dubbo系列笔记之XML配置文件解析流程
其实在spring源码的BeanFactory注释的头伊始,就已经说明了bean的生命周期:
Dubbo系列笔记之XML配置文件解析流程
好了,言归正传,这次写的篇幅可能有点长,最后我们快点叨叨一下bean的实例化过程吧。
在此之前,我们和bean的注册结合起来,其实bean的初始化就相当于一个造书的过程。解析配置信息时相当于我们要写一本书时,先有书的内容,这些配置信息就是bean的内容。有了书的内容后,我们要把内容写在一页一页的纸上,相当于一个个bean的一个个属性,而这些“纸”合起来就是BeanDefinition。然后我们要把这些稿纸给到工厂,那就要有一个人去保存这些纸了,这个人就是bean的注册表。在这个人手里每一堆稿纸都对应一个书名,就是bean的名字,这样我们就完成了bean的注册过程。接下来就是要把这些稿纸交给工厂去装订成一本真正的书,那这个过程就是bean的实例化。
- bean什么时候会实例化?
这里我们再做一个延伸,spring bean在什么时候会进行实例化呢?
第一:如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean的时候实例化
第二:如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
代码语言:javascript复制(1):如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使用该Bean的时候,直接从这个缓存中取
(2):如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
(3):如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
- bean的实例化过程
一张图说明,
Dubbo系列笔记之XML配置文件解析流程
在本文的最后,放一张Spring容器从加载配置文件到创建出一个完整Bean的作业流程:
Dubbo系列笔记之XML配置文件解析流程
来源:https://www.tuicool.com/articles/Ubu6zyf