Dubbo与Springboot集成过程

525 阅读6分钟

1.简介

得益与Spring的“微内核与扩展机制“,第三方框架能够很方便的和Spring进行集成。

这篇笔记,我们就通过分析"Dubbo通过Spring导出服务的过程“,来见微知著,体会下Spring是如何和第三方框架进行集成的,关注Spring提供了哪些扩展点。

相关组件版本:

		<parent>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-parent</artifactId>
                        <version>2.4.0</version>
                        <relativePath/> <!-- lookup parent from repository -->
		</parent>
		<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo -->
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
			<version>2.7.3</version>
		</dependency>


		<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
		<!-- 使用0.11版本会有兼容问题 -->
		<dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>0.10</version>
		</dependency>

		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>4.2.0</version>
			<exclusions>
				<exclusion>
					<groupId>org.apache.zookeeper</groupId>
					<artifactId>zookeeper</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

2.ServiceBean.class

​ 通过查阅Dubbo官方文档,知道了Dubbo导出服务的入口方式是ServiceBean的onApplicationEvent方法。

​ 先说明下ServiceBean其实就是每个具体的dubbo服务的封装,比如下面这个dubbo provider的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">


<!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="hello-world-app"  />

    <!-- 使用zk广播注册中心暴露服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.huang.dubbo.api.DemoService" ref="demoServiceImpl" />

<!--    &lt;!&ndash; 和本地bean一样实现服务 &ndash;&gt;-->
<!--    <bean id="demoService" class="com.huang.dubboprovider.service.DemoServiceImpl" />-->
</beans>			

​ ” <dubbo:service interface="com.huang.dubbo.api.DemoService" ref="demoServiceImpl" />“最终就会被封装成一个ServiceBean。

​ onApplicationEvent方法是ServiceBean实现于ApplicationListener接口,ApplicationListener就是是Spring提供的一个扩展点,实现了这个接口的spring bean,能够订阅Spring容器的指定事件,当Spring广播指定的事件之后,对应的spring bean就可以调用onApplicationEvent方法来执行自己事先计划好的逻辑。

​ 我们在ServiceBean的onApplicationEvent方法上打上断点,启动Spring项目。通过调用栈可以发现,Spring容器在启动的过程中会调用refreshContext方法,这个方法内会执行很多关键的逻辑,本篇笔记就先不赘述了,我们只要知道Spring执行refreshContext完毕(finishRefresh)之后,Spring容器会广播一个ContextRefreshedEvent事件,ServiceBean订阅了这个事件,就会执行相应的逻辑,这其实是一个观察者模式的实现。Spring通过观察者模式,让第三方框架通过指定的事件和Spring容器进行交互。最后ServiceBean调用export()方法,来将对应的dubbo服务注册到注册中心上去。

image-20201115174554080

​ Spring应用的事件都继承自ApplicationEvent,除了ContextRefreshedEvent之外,还有其他很多可以被监听的事件,开发者可以根据需要,通过订阅这些在Spring应用生命周期中的事件,来与Spring进行交互且无需修改Spring的代码。这就是Spring扩展机制的美妙之处XD。

image-20201115175614063

3.Spring容器创建ServiceBean过程

​ 从上文第二章我们已经知道,dubbo服务是通过ServiceBean导出并注册到指定的注册中心上去的,这个算是”果“,还要搞清楚”因“才行。什么是”因“呢?Spring容器如何从dubbo的provider.xml文件中读取配置,并创建对应的ServiceBean实例,这个就是”因“,明白了这个,我们才能够拍着胸脯说:”我知道dubbo是如何和Spring进行集成的!“。

​ dubbo要暴露的服务配置在provider.xml文件中(当然dubbo也有提供java代码配置服务的方式,本篇笔记就不多赘述。)。我们都知道,Spring除了能够通过代码注解来配置创建bean,也能够通过xml文件来配置bean,然后让Spring容器读取xml文件来创建bean。这个时候你要说了,Spring bean的xml文件中,配置一个bean的节点不应该用”“标签吗,怎么dubbo的配置文件中,用的标签是”dubbo:service/“?这个其实是Spring的XML Schema-based configuration机制,想详细了解的可以看下官方文档,简单点说,这个机制的作用就是让bean配置文件更加的通用且方便。用户可以定义自己的xml标签,然后通过自定义的XSD文件来校验。Spring容器在读取用户自定义的标签时肯定是不认识的,所以用户还需要为自定义的xml文件实现NamespaceHandler和BeanDefinitionParser,来解析xml文件中的标签。

​ 有了上述的知识铺垫之后,我们来,看看ServiceBean是如何被Spring读取并注入到Spring容器中的。这里就不完全把debug时的各种尝试给描述出来了。因为一开始debug也没思路,只能抓着已知的ServiceBean来下手,看看dubbo代码中有哪些地方引用了ServiceBean,再去相关(看包名、类名、实现了哪些接口、父类、方法名)的代码上打上断点来调试,把这个调试的过程全部说出来可太费篇幅了。

​ 我是使用@ImportResource来让Spring容器加载dubbo的配置文件到容器中。

package com.huang.dubboprovider.config;


import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ImportResource({"classpath:dubbo-provider.xml"})
public class ImportDubboProviderConfig {
}
		

​ dubbo-provider.xml中各个ServiceBean实例被创建被注入Spring容器的过程如下图所示(源码就不贴了,贴上去的话啰啰嗦嗦一大段,而且看起来也很费劲。只给个大概的流程就好,说清楚关键类名,以后再次debug也很方便,不会晕头转向):

image-20201115192435394

​ 这个过程中,dubbo通过Spring的SPI,将dubbo xml文件的NamespaceHandler指定成了DubboNamespaceHandler,具体过程如下:

​ DefaultNamespaceHandlerResolver,读取当前classloader中所有的META-INF/spring.handlers文件,spring.handlers中记录了namespaceUri和具体NamespaceHandler之间的映射关系。

image-20201115193047204

image-20201115193402769

​ 所以解析dubbo-provider.xml的时候会自动使用DubboNamespaceHandler。

​ DubboNamespaceHandler继承自NamespaceHandlerSupport,NamespaceHandlerSupport实现了NamespaceHandler接口。NamespaceHandler中只有一个init方法,DubboNamespaceHandler在init方法中,将xml文件中各种类型的标签标签和他们对应的BeanDefinitionParser建立起映射关系,保存到NamespaceHandlerSupport.parsers(一个HashMap)中。

image-20201115194102192

然后通过NamespaceHandlerSupport.parsers给dubbo xml文件总不同类型的标签选择不同类型的DubboBeanDefinitionParser来解析。Spring在这个过程中,给用户留下了两个”钩子“,一个是NamespaceHandler的inti方法,另一个是NamespaceHandlerSupport的parsers字段,这种留”钩子“的方式,本质上来说应该是”模板方法模式“,提供方(父类)创建好模板,留下钩子共使用方(子类)实现。

4.总结

​ 通过分析dubbo集成Spring,将服务导出并注册到注册中心的过程。我们知道了,在这个过程中Spring至少提供了三种方式,方便用户扩展或者自定义Spring的行为:事件机制(观察者模式、发布订阅模式)、SPI机制、模板方法模式(钩子),当然Spring的扩展点可远不止上述三种,不过都万变不离其宗。核心的方式还是”面向接口编程“,将功能细分抽象成接口,用户通过接口扩展或者改变Spring的行为。好好体会”面向接口编程“这种方式,对于我们构建易于扩展、维护的程序会很有帮助。

5.源码地址

dubbo与Springboot集成项目的源码地址(拿来学习dubbo,debug用的):github.com/ambition080…

6.参考

扩展Spring系列(1)--Spring 的微内核与FactoryBean扩展机制

Dubbo官方文档-服务导出

XML Schema-based configuration

XSD介绍