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

本贴最后更新于 1679 天前,其中的信息可能已经时移俗易

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


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


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

一、约束文件 schema

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

image.png

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

image.png

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

image.png

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

二、spring.handlers 和 spring.schemas

当 spring 解析 xml 时遇到自定义的标签时,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 的标签。

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

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


通则达济天下,谋则远虑古今。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    941 引用 • 1458 回帖 • 150 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 607 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...