简单叨叨一下 Dubbo 是如何自定义标签给 spring 承载 bean 的。
Spring 通过 XML 解析程序将其解析为 DOM 树,通过 NamespaceHandler 指定对应的 Namespace 的 BeanDefinitionParser 将其转换成 BeanDefinition。再通过 Spring 自身的功能对 BeanDefinition 实例化对象。Dubbo 做的只是实现了 NamespaceHandler 解析成 BeanDefinition。
好了,总结起来就这么简单,下面我们具体来看一下。
一、约束文件 schema
下面是一个标准的 schema 文件头的格式 👇
首先自定义的标签会有一些约束规范,比如我自定义的有哪几种标签,标签里面有哪些属性等等,在 XML 中每个命名空间都会有一个.xsd 的约束文件。
一个约束文件.xsd 长得像下面这样 👇
里面限制自定义的标签里面有哪些属性,属性的类型是什么啊这种。
二、spring.handlers 和 spring.schemas
当 spring 解析 xml 时遇到自定义的标签时,spring 会加载 spring.handlers 和 spring.schemas 这两个文件,两个文件长下面这样 👇
❀ spring.schemas:里面指定了该标签的约束文件本地路径,在解析XML文件时将XSD重定向到本地文件,避免在解析XML文件时需要上网下载XSD文件。通过实现org.xml.sax.EntityResolver接口来实现该功能。
❀ spring.handlers:里面指定了由那个handler去处理这些自定义的标签,实现一个handler需要实现org.springframework.beans.factory.xml.NamespaceHandler接口,或使用org.springframework.beans.factory.xml.NamespaceHandlerSupport的子类。
三、DubboNamespaceHandler
在 spring 加载完 spring.handlers 后就知道要通过 DubboNamespaceHandler 去解析 这种 dubbo 的标签。
下面我们来看一下,它长这个样子 👇
通过上面代码我们可以知道,解析标签的工作并不是 namespaceHandler 去做的,它做的只是为每个标签注册 BeanDefinitionParser,告诉 spring 哪个 BeanDefinitionParser 去真正处理这个标签。
四、DubboBeanDefinitionParser
下面我们简单来看一下 DubboBeanDefinitionParser 长什么样,嗯,大概就下面这个样子吧 👇
在解析标签时 spring 会调用 BeanDefinitionParser 的 parse()方法去生成一个 BeanDefinition。
我们注意到,parse()方法有两个参数 Element element 和 ParserContext parserContext,element 是 xml 解析器在解析完 xml 标签后将其组装成一个这样的对象,而第二个参数 parserContext,我们通过他的 getRegistry()方法获取 BeanDefinitionRegistry 对象。他长下面这个样 👇
说到这里,那么我们解析配置的初衷是什么呢?
没错,我们为了把配置解析成 bean 去交给 spring 托管。
而 BeanDefinitionRegistry 的作用主要是向 spring 注册表中注册 BeanDefinition 实例,通过调用其 registerBeanDefinition()方法完成注册的过程。
简单看一下 BeanDefinitionRegistry 长什么样子 👇
可以看到 BeanDefinitionRegistry 是一个接口,它定义了一些注册 BeanDefinition 的一些必要方法。
那么具体 BeanDefinitionRegistry 是如何注册 bean 的呢?
我们看一下它的 registerBeanDefinition(String var1, BeanDefinition var2),第一个参数是 bean 的 id,第二个就是 BeanDefinition,BeanDefinition 描述了一个 bean 的画像,他是基础的 bean 定义接口。由他衍生出 AbstractBeanDefinition 和 RootBeanDefinition。
五、BeanDefinition
- AbstractBeanDefinition
他长得挺长的,主要是在 BeanDefinition 的基础上定义了一些属性,基本囊括了 Bean 实例化需要的所有信息。如下,👇
总体来看这个抽象类长了这些东西:
- Bean 的描述信息(例如是否是抽象类、是否单例)
- depends-on 属性(String 类型,不是 Class 类型)
- 自动装配的相关信息
- init 函数、destroy 函数的名字(String 类型)
- 工厂方法名、工厂类名(String 类型,不是 Class 类型)
- 构造函数形参的值
- 被 IOC 容器覆盖的方法
- Bean 的属性以及对应的值(在初始化后会进行填充)
- RootBeanDefinition
从 spring2.5 开始,spring 一开始都是使用 GenericBeanDefinition 类保存 Bean 的相关信息,在需要时,在将其转换为其他的 BeanDefinition 类型。
这里两个 BeanDefinition 可以说是互补的关系,👇
我们可以在源码中看到,RootBeanDefinition 继承了 AbstractBeanDefinition,在其基础上面定义了更多属性。从上图可以看到第一个属性 BeanDefinitionHolder,那么这个 BeanDefinitionHolder 是什么东西呢,👇
它保存了 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 中它有三个实现,如下图,
我们分别看一下这三个类怎么实现的:👇
- DefaultListableBeanFactory
首先,是做了一下校验,判断,看要注册的 bean 是否已经存在了等等。注意到它有两个关键的成员变量:
其中 beanDefinitionMap 这个 ConcurrentHashMap 注册表,而 beanDefinitionNames 显而易见是 beanName 的集合。
观察到,注册 bean 的最重要步骤就是 this.beanDefinitionMap.put(beanName, beanDefinition);
。
- GenericApplicationContext
再来看一下 BeanDefinitionRegistry 的第二个实现类 GenericApplicationContext:
注册 bean 的代码实现只有一行,可以看到他只是调用了上面 DefaultListableBeanFactory 的注册方法。
- SimpleBeanDefinitionRegistry
最后一个,SimpleBeanDefinitionRegistry。它是这样的一个东西:👇
比较关键的一句就是红框所示。
这样下来,我们知道,注册 bean 最关键的就是往注册表的 ConcurrentHashMap 中 put 进去 bean 的 name 和 BeanDefinition。
七、bean 的实例化
到这里为止,我们由 Dubbo 的 xml 配置文件解析,延伸到了 spring 如何注册 bean。那么纵观 bean 的整个生命周期,bean 的初始化可以说是为 bean 的一生埋下了种子,那么最后我们再看看 bean 初始化的另一个动作---bean 注册后是如何被实例化的。附一张 bean 生命周期全图,顺便看看 bean 宝宝长大了都要做一些什么:
其实在 spring 源码的 BeanFactory 注释的头伊始,就已经说明了 bean 的生命周期:👇
好了,言归正传,这次写的篇幅可能有点长,最后我们快点叨叨一下 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 的实例化过程
一张图说明,👇
在本文的最后,放一张 Spring 容器从加载配置文件到创建出一个完整 Bean 的作业流程:
通则达济天下,谋则远虑古今。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于