这里debug的代码用的是github上dubbo项目的dubbo-demo里的dubbo-demo-xml下的代码。这里以默认的dubbo通信协议为debug的代码,由于这一篇都是dubbo框架内部实现,所以建议先看下dubbo官网上的一篇讲dubbo的设计原则的文章,有助于理解代码,这里先贴出dubbo官网上的架构图,然后我们在看代码时,比对架构图一起分析:
下面是架构图中的各层说明:
- config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
- protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
demo里的consumer端的xml配置:
<beans
// xmlns:xsi是xsi标签命名空间
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// xmlns:dubbo是dubbo标签的命名空间
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
// 当前那xml文件默认的命名空间
xmlns="http://www.springframework.org/schema/beans"
// xsi:schemaLocation 配置了每个命名空间对应里配置规范,用来做格式校验
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="demo-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" timeout="6000"/>
<!-- 协议配置-->
<dubbo:protocol name="dubbo"/>
<!-- consumer配置-->
<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService" timeout="6000" />
</beans>
复制代码
1、将dubbo:reference
配置解析成BeanDefinition
Dubbo的consumer配置是<dubbo:reference>
,在《spring解析dubbo标签配置过程》中可以看到这个配置被解析成BeanDefinition
类的过程,时序图如下:
2、通过BeanDefinition实例化ReferenceConfig
对象
BeanDefinition
的BeanClass属性值为org.apache.dubbo.config.spring.ReferenceConfig.class
类,该类实现了FactoryBean
接口(FactoryBean
接口可以说就是为了这种通过动态代理技术生成bean的场景设计的,Spring在注入时,会注入该对象的getObject()
方法返回真正的实例)。getBean()
详细逻辑见这里,debug截图如下:
3、依赖注入时,触发ReferenceConfig
这个FactoryBean
的getObject()
调用,返回代理类
Spring在依赖注入时也会调用getBean(String beanName)
方法来获取已经实例化的bean,通过beanName会获取第二步中实例化的ReferenceConfig
对象,而该对象是BeanFactory
,所以最终会调用getObject()
方法,debug截图如下:
getObject()
截图如下:
getObject()
代码如下,我这里省略了部分分支代码:
public Object getObject() {
return this.get();
}
public synchronized T get() {
...
this.init();
...
return this.ref;
}
public synchronized void init() {
// 初始化dubbo启动类
if (this.bootstrap == null) {
this.bootstrap = DubboBootstrap.getInstance();
this.bootstrap.init();
}
...
// map保存所有创建consumer代理类的相关配置信息
Map<String, String> map = new HashMap();
map.put("side", "consumer");
// 往map中插入dubbo版本号、时间戳、进程pid运行时信息
ReferenceConfigBase.appendRuntimeParameters(map);
...
// 传入map,创建代理类
this.ref = this.createProxy(map);
...
}
private T createProxy(Map<String, String> map) {
...
// 检查注册中心配置,并根据注册中心配置生成注册中心的URL
this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
u = (URL)var3.next();
monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
if (monitorUrl != null) {
map.put("monitor", URL.encode(monitorUrl.toFullString()));
}
}
}
...
// 构建invoker链
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
...
// 通过ProxyFactory创建代理
return PROXY_FACTORY.getProxy(this.invoker);
}
复制代码
下面是结合dubbo架构图画的主要步骤的时序图:
3.1 初始化Dubbo启动类DubboBootstrap
这个DubboBootstrap
是dubbo的启动类,类似于springboot
的SpringApplication
类,存储了整个dubbo环节的各种配置信息,负责provider
的服务暴露及consumer
的服务订阅、维护dubbo容器的生命周期,这里先跳过,继续研究我们的consumer
实例化过程
3.2 构建注册中心对象
代码在createProxy(Map<String, String> map)
方法里,核心代码如下
this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
// 获取注册中心的URL,并将当前consumer配置的元数据信息放到URL的Parameter属性里
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
...
}
}
复制代码
<dubbo:registry>
配置中心对应的bean是RegistryConfig.class
,解析过程和<dubbo:reference>
一样,被解析出来的配置都会放到一个全局的ConfigManager
中,这里会从这个ConfigManager
中读取注册中心配置。dubbo里的常见的配置都会缓存起来,在《spring的xml文件里dubbo标签解析过程》里可以看到负责解析dubbo自定义标签的是DubboNamespaceHandler
,每种配置对应的实体类如下:
这些类都继承了AbstractConfig
类,这个抽象类有个被@PostConstruct
注解标注的addIntoConfigManager()
方法(该注解会告诉Spring容器在实例化该对象后,会执行注解标注的方法),会将当前对象缓存到一个全局的ConfigManager
对象中:
@PostConstruct
public void addIntoConfigManager() {
ApplicationModel.getConfigManager().addConfig(this);
}
复制代码
Url
可以说是dubbo主链路里分量非常重的一个参数封装类,在主链路里封装参数一路传递,这里就是讲注册中心的元数据与consumer的元数据封装到一个URL中,传递到下游的方法里,主要存储一些元数据信息,这里就封装了注册中心地址、consumer的接口、方法等信息,debug截图如下:
private static final Protocol REF_PROTOCOL = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
复制代码