码恋 码恋

ALL YOUR SMILES, ALL MY LIFE.

目录
Dubbo系列笔记之XML配置文件解析流程
/    

Dubbo系列笔记之XML配置文件解析流程

简单叨叨一下Dubbo是如何自定义标签给spring承载bean的。


Spring通过XML解析程序将其解析为DOM树,通过NamespaceHandler指定对应的Namespace的BeanDefinitionParser将其转换成BeanDefinition。再通过Spring自身的功能对BeanDefinition实例化对象。Dubbo做的只是实现了NamespaceHandler解析成BeanDefinition。


好了,总结起来就这么简单,下面我们具体来看一下。

一、约束文件schema

下面是一个标准的文件头的格式

image.png

首先自定义的标签会有一些约束规范,比如我自定义的有哪几种标签,标签里面有哪些属性等等,在XML中每个命名空间都会有一个.xsd的约束文件。

image.png

一个约束文件.xsd长得像下面这样

image.png

里面限制自定义的标签里面有哪些属性,属性的类型是什么啊这种。

二、spring.handlers和spring.schemas

当spring解析xml时遇到自定义的标签时,会调用BeanDefinitionParserDelegate.parseCustomElement(...)方法,如下:

image.png

可以看到接下来通过DefaultNamespaceHandlerResolver.resolve(String namespaceUri)获得对应的Dubbo处理器。这个namespaceUri就是bubbo的命名空间uri,spring会去查找名字为spring.handlers的文件,里边配置了命名空间对应的handler,如下面:

spring会加载spring.handlers和spring.schemas这两个文件,两个文件长下面这样

❀  spring.schemas:里面指定了该标签的约束文件本地路径,在解析XML文件时将XSD重定向到本地文件,避免在解析XML文件时需要上网下载XSD文件。通过实现org.xml.sax.EntityResolver接口来实现该功能。

image.png

❀ spring.handlers:里面指定了由那个handler去处理这些自定义的标签,实现一个handler需要实现org.springframework.beans.factory.xml.NamespaceHandler接口,或使用org.springframework.beans.factory.xml.NamespaceHandlerSupport的子类。

image.png

三、DubboNamespaceHandler

在spring加载完spring.handlers后就知道要通过DubboNamespaceHandler去解析dubbo:xxx这种dubbo的标签。

下面我们来看一下,它长这个样子

image.png

通过上面代码我们可以知道,解析标签的工作并不是namespaceHandler去做的,它做的只是为每个标签注册BeanDefinitionParser,告诉spring哪个BeanDefinitionParser去真正处理这个标签。

四、DubboBeanDefinitionParser

下面我们简单来看一下DubboBeanDefinitionParser长什么样,嗯,大概就下面这个样子吧

image.png

在解析标签时spring会调用BeanDefinitionParser的parse()方法去生成一个BeanDefinition。

我们注意到,parse()方法有两个参数Element element和ParserContext parserContext,element是xml解析器在解析完xml标签后将其组装成一个这样的对象,而第二个参数parserContext,我们通过他的getRegistry()方法获取BeanDefinitionRegistry对象。他长下面这个样

image.png

说到这里,那么我们解析配置的初衷是什么呢?

没错,我们为了把配置解析成bean去交给spring托管。

而BeanDefinitionRegistry的作用主要是向spring注册表中注册 BeanDefinition 实例,通过调用其registerBeanDefinition()方法完成注册的过程。

简单看一下BeanDefinitionRegistry长什么样子

image.png

可以看到BeanDefinitionRegistry是一个接口,它定义了一些注册BeanDefinition的一些必要方法。

那么具体BeanDefinitionRegistry是如何注册bean的呢?

我们看一下它的registerBeanDefinition(String var1, BeanDefinition var2),第一个参数是bean的id,第二个就是BeanDefinition,BeanDefinition描述了一个bean的画像,他是基础的bean定义接口。由他衍生出AbstractBeanDefinition和RootBeanDefinition。

五、BeanDefinition

  • AbstractBeanDefinition

他长得挺长的,主要是在BeanDefinition的基础上定义了一些属性,基本囊括了Bean实例化需要的所有信息。如下,

image.png

总体来看这个抽象类长了这些东西:

  1. Bean的描述信息(例如是否是抽象类、是否单例)
  2. depends-on属性(String类型,不是Class类型)
  3. 自动装配的相关信息
  4. init函数、destroy函数的名字(String类型)
  5. 工厂方法名、工厂类名(String类型,不是Class类型)
  6. 构造函数形参的值
  7. 被IOC容器覆盖的方法
  8. Bean的属性以及对应的值(在初始化后会进行填充)
  • RootBeanDefinition

从spring2.5开始,spring一开始都是使用GenericBeanDefinition类保存Bean的相关信息,在需要时,在将其转换为其他的BeanDefinition类型。

这里两个BeanDefinition可以说是互补的关系,

image.png

我们可以在源码中看到,RootBeanDefinition继承了AbstractBeanDefinition,在其基础上面定义了更多属性。从上图可以看到第一个属性BeanDefinitionHolder,那么这个BeanDefinitionHolder是什么东西呢,

image.png

它保存了bean的名字、别名、以及BeanDefinition持有的bean的一些基础信息。

总结一下:

  1. 定义了id、别名与Bean的对应关系(BeanDefinitionHolder)
  2. Bean的注解(AnnotatedElement)
  3. 具体的工厂方法(Class类型),包括工厂方法的返回类型,工厂方法的Method对象
  4. 构造函数、构造函数形参类型
  5. Bean的class对象

那么我这里我们就大概了解了一些BeanDefinition里面有什么东西,那回到上面问题,bean具体是怎么注册的呢?

六、BeanDefinitionRegistry

在上面我们简单看了一下BeanDefinitionRegistry这个接口中有一些方法。下面我们来着重看一下注册方法是怎么实现的void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;。点开这个方法的实现,我们可以看到spring中它有三个实现,如下图,

image.png

我们分别看一下这三个类怎么实现的:

  1. DefaultListableBeanFactory

image.png

首先,是做了一下校验,判断,看要注册的bean是否已经存在了等等。注意到它有两个关键的成员变量:

image.png

其中beanDefinitionMap这个ConcurrentHashMap注册表,而beanDefinitionNames显而易见是beanName的集合。

观察到,注册bean的最重要步骤就是this.beanDefinitionMap.put(beanName, beanDefinition);

  1. GenericApplicationContext

再来看一下BeanDefinitionRegistry的第二个实现类GenericApplicationContext:

image.png

注册bean的代码实现只有一行,可以看到他只是调用了上面DefaultListableBeanFactory的注册方法。

  1. SimpleBeanDefinitionRegistry

最后一个,SimpleBeanDefinitionRegistry。它是这样的一个东西:

image.png

比较关键的一句就是红框所示。

这样下来,我们知道,注册bean最关键的就是往注册表的ConcurrentHashMap中put进去bean的name和BeanDefinition。

七、bean的实例化

到这里为止,我们由Dubbo的xml配置文件解析,延伸到了spring如何注册bean。那么纵观bean的整个生命周期,bean的初始化可以说是为bean的一生埋下了种子,那么最后我们再看看bean初始化的另一个动作---bean注册后是如何被实例化的。附一张bean生命周期全图,顺便看看bean宝宝长大了都要做一些什么:

beanall.png

其实在spring源码的BeanFactory注释的头伊始,就已经说明了bean的生命周期:

image.png

好了,言归正传,这次写的篇幅可能有点长,最后我们快点叨叨一下bean的实例化过程吧。

在此之前,我们和bean的注册结合起来,其实bean的初始化就相当于一个造书的过程。解析配置信息时相当于我们要写一本书时,先有书的内容,这些配置信息就是bean的内容。有了书的内容后,我们要把内容写在一页一页的纸上,相当于一个个bean的一个个属性,而这些“纸”合起来就是BeanDefinition。然后我们要把这些稿纸给到工厂,那就要有一个人去保存这些纸了,这个人就是bean的注册表。在这个人手里每一堆稿纸都对应一个书名,就是bean的名字,这样我们就完成了bean的注册过程。接下来就是要把这些稿纸交给工厂去装订成一本真正的书,那这个过程就是bean的实例化。

  • bean什么时候会实例化?

这里我们再做一个延伸,spring bean在什么时候会进行实例化呢?

第一:如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean的时候实例化

第二:如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:

(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的实例化过程

一张图说明,

beanshilihua.jpg

在本文的最后,放一张Spring容器从加载配置文件到创建出一个完整Bean的作业流程:

image.png




❤ 转载请注明本文地址或来源,谢谢合作 ❤


center