1.概述
本篇文章,我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程大致可分为三个部分:第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。本篇文章将会对这三个部分代码进行详细的分析。
2.源码
服务导出的入口为:DubboBootstrap.exportServices(),首先会遍历解析完成的所有的ServiceConfig,然后进行服务的导出。
private void exportServices() {
configManager.getServices().forEach(sc -> {
// TODO, compatible with ServiceConfig.export()
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
if (exportAsync) {
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
// 导出服务
sc.export();
exportedServices.add(sc);
});
asyncExportingFutures.add(future);
} else {
sc.export();
exportedServices.add(sc);
}
});
}
ServiceConfig.export()
public synchronized void export() {
// 通过export判断是不是要暴露
if (!shouldExport()) {
return;
}
// 如果bootstrap没有初始化,则初始化
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
// 校验和更新一些子配置,例如:设置默认provider、设置默认protocol、检查ref是不是为空,接口类型是不是匹配等
checkAndUpdateSubConfigs();
//init serviceMetadata
serviceMetadata.setVersion(getVersion());
serviceMetadata.setGroup(getGroup());
serviceMetadata.setDefaultGroup(getGroup());
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setServiceInterfaceName(getInterface());
serviceMetadata.setTarget(getRef());
// 延迟暴露
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 执行export
doExport();
}
// 调度已经暴露事件
exported();
}
2.1 前置工作
前置工作主要包含两个部分,分别是配置检查,以及 URL 装配(dubbo通过采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息)。
在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省默认配置。
2.1.1 检查配置
export 方法对两项配置进行了检查,并根据配置执行相应的动作。首先是 export 配置,这个配置决定了是否导出服务。有时候我们只是想本地启动服务进行一些调试工作,我们并不希望把本地启动的服务暴露出去给别人调用。此时,我们可通过配置 export 禁止服务导出
<dubbo:provider export="false" />
// 通过export判断是不是要暴露
if (!shouldExport()) {
return;
}
校验和更新一些子配置,例如:设置默认provider、设置默认protocol、检查ref是不是为空,接口类型是不是匹配等,配置检查并非本文重点,因此接下来重点介绍doExportUrls 方法
private void checkAndUpdateSubConfigs() {
// Use default configs defined explicitly with global scope
completeCompoundConfigs();
// 设置默认provider
checkDefault();
// 设置默认protocol
checkProtocol();
// init some null configuration.
List<ConfigInitializer> configInitializers = ExtensionLoader.getExtensionLoader(ConfigInitializer.class)
.getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
configInitializers.forEach(e -> e.initServiceConfig(this));
// if protocol is not injvm checkRegistry
if (!isOnlyInJvm()) {
checkRegistry();
}
// 刷新类属性,通过set方法或者@Paramter注解设置类属性
this.refresh();
if (StringUtils.isEmpty(interfaceName)) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
// 泛化处理服务和普通服务类
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, getMethods());
// 检查ref是不是为空,接口类型是不是匹配
checkRef();
generic = Boolean.FALSE.toString();
}
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
checkStubAndLocal(interfaceClass);
ConfigValidationUtils.checkMock(interfaceClass, this);
ConfigValidationUtils.validateServiceConfig(this);
postProcessConfig();
}
导出服务
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
// 导出服务
doExportUrls();
}
2.1.2 多协议多注册中心导出服务
Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。相关代码如下:
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
// 加载注册中心链接
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);