Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。
整个逻辑大致可分为三个部分:
第一部分是前置工作,主要用于检查配置参数,组装 URL。
第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。
第三部分是向注册中心注册服务,用于服务发现;
实现的效果:
- Main()方法启动后:
- 开发了20880端口(默认端口)
- 发布了一个可远端调用的地址:dubbo:// 192.168.1.1:20880/interface?…… (发布到了注册中心)
------------------
这个过程都需要做哪些事情:
- 解析配置文件–>基于spring标签进行的扩展
- 暴露本地服务/远程服务
- 启动netty --> 提供netty服务 暴露端口
- 打开连接zk
- 把地址注册到zk
- 监听zk
1、解析配置
spring里面提供了很多扩展
dubbo-config-spring模块里面提供了配置文件的扩展
spring提供了两个可扩展的接口或类:
根据SPI扩展机制进入DubboNamespaceHandler解析配置文件,进入到serviceBean 和ServiceConfig中,开始进行导出服务的前置工作;通过onApplicationEvent 监听事件,检查服务是否发布,调用export导出服务,一下是对export的分析:
1.1 检查配置
- 检查的interface属性是否合法
- 检查providerConfig和ApplicationConfig等核心配置类对象是否为空,为空会从其他配置类获取对应的实例;
- 检查并处理泛化服务和普通服务类
- 检查本地存根配置
- 对ApplicationConfig、RegisterConfig等配置类进行检测,为空则尝试创建,无法创建则抛异常;
1.2 多协议多注册中心导出服务
private void doExportUrls() {
// 加载注册中心链接
List<URL> registryURLs = loadRegistries(true);
// 遍历 protocols,并在每个协议下导出服务
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
1.3 组装URL
走到这一步url大概是这个样子:_ registry://192.168.22.130:2181/org.apache.dubbo.registry.RegistryService/……_
这一步是根据配置信息组装url, 而且url驱动流程的执行,会再各个模块之间一直往下传,后续会不断修改URL头或协议地址,并根据url地址中的信息做相应的处理;
在doExportUrlsFor1Protocol方法中做了url的组装处理,通过反射的方式获取到版本、时间戳、方法名以及各种配置对象的字段信息,然后放入map,源码太长了,这块只贴一下伪代码帮助理解:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 获取 ArgumentConfig 列表
for (遍历 ArgumentConfig 列表) {
if (type 不为 null,也不为空串) {
// 分支1
1. 通过反射获取 interfaceClass 的方法列表
for (遍历方法列表) {
1. 比对方法名,查找目标方法
2. 通过反射获取目标方法的参数类型数组 argtypes
if (index != -1) {
// 分支2
1. 从 argtypes 数组中获取下标 index 处的元素 argType
2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
} else {
// 分支3
1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
2. 添加 ArgumentConfig 字段信息到 map 中
}
}
} else if (index != -1) {
// 分支4
1. 添加 ArgumentConfig 字段信息到 map 中
}
}
}
2. 导出Dubbo服务
准备工作做完了,接下来进行服务导出。服务导出分为导出到本地JVM和导出到远程。
代码根据 url 中的 scope 参数决定服务导出方式,分别如下:
- scope = none,不导出服务
- scope != remote,导出到本地
- scope != local,导出到远程
服务发布的本质就是把export的每个服务转为一个对应的Invoker可执行体,然后把转换后的Invoker都放到一个exporterMap(key,invoker)集和中;
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 省略无关代码
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
// 如果 scope = none,则什么都不做
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// scope != remote,导出到本地
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// scope != local,导出到远程
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
if (registryURLs != null && !registryURLs.isEmpty()) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
// 加载监视器链接
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
// 将监视器链接作为参数添加到 url 中
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
// 为服务提供类(ref)生成 Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 导出服务,并生成 Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
// 不存在注册中心,仅导出服务
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。
2.1 Invoker创建过程
Invoker是一个重要的模型,在服务的提供端和调用端都会出现invoker.
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
Invoker由ProxyFactory创建而来,Dubbo默认的ProxyFactory实现类是javassistProFactory。来看一下javassistProFactory类创建Invoker的过程
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 为目标类创建 Wrapper
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName