前言
Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 收到事件后,会立即执行导出逻辑。整个逻辑大概可以分成三个部分:每一部分是前置工作,主要用于检查参数和组装URL。第二部分是导出服务,包含导出到本地(JVM),和导出服务到远程。第三个部分是向注册中心注册服务,用于服务发现。以下源码分析基于 Dubbo 2.7.2。
源码分析
服务导出的入口在 ServiceBean
的 onApplicationEvent
。onApplicationEvent
是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 判断是否已经导出或者已经取消导出
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
这个方法主要是检查是否需要导出服务,如果服务已经导出了,或者已经取消导出的话,就不需要导出服务了。旧的Dubbo 版本这里还有一个是延迟导出的判断,在最新版的 Dubbo 中,这个检查条件放在了后面。
我们在来看 export
中的源码:
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
/**
* 父类 ServiceConfig 的 export 方法
*/
public synchronized void export() {
// 检查和更新检查
checkAndUpdateSubConfigs();
// 是否需要导出
if (!shouldExport()) {
return;
}
// 是否需要延迟导出
if (shouldDelay()) {
delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
前置工作
前置工作主要是包含配置检查以及 URL 组装。在导出服务前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成以后,需要把这些配置组装到 URL 中。在 Dubbo 中,URL 的作用非常重要,Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获得配置。采用 URL 作为配置信息的统一格式,所有的扩展点都是通过传递 URL 携带配置信息。
检查和更新配置
我们从 export
方法中的 checkAndUpdateSubConfigs
中开始分析。
public void checkAndUpdateSubConfigs() {
// Use default configs defined explicitly on global configs
// 使用全局配置来显示定义默认配置。
completeCompoundConfigs();
// Config Center should always being started first.
// 配置中心首先应该被启动
startConfigCenter();
// 检查默认项
checkDefault();
// 检查协议
checkProtocol();
// 检查应用信息
checkApplication();
// if protocol is not injvm checkRegistry
// 检查 protocol 是否是本地协议
if (!isOnlyInJvm()) {
// 如果不是本地协议,则需要验证注册中心
checkRegistry();
}
this.refresh();
checkMetadataReport();
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, methods);
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);
checkMock(interfaceClass);
}
我们开始一步一步的分析上面的源码。
- 获取全局配置信息 completeCompoundConfigs
首先我们检测 provider 、 application 等核心配置类对象是否为空。如果为空的话,则尝试从其它配置类对象中获得相应的实例。
private void completeCompoundConfigs() {
// provider 不为空,指定了 provider
if (provider != null) {
if (application == null) {
setApplication(provider.getApplication());
}
if (module == null) {
setModule(provider.getModule());
}
if (registries == null) {
setRegistries(provider.getRegistries());
}
if (monitor == null) {
setMonitor(provider.getMonitor());
}
if (protocols == null) {
setProtocols(provider.getProtocols());
}
if (configCenter == null) {
setConfigCenter(provider.getConfigCenter());
}
}
// 指定 module
if (module != null) {
if (registries == null) {
setRegistries(module.getRegistries());
}
if (monitor == null) {
setMonitor(module.getMonitor());
}
}
// 指定 application
if (application != null) {
if (registries == null) {
setRegistries(application.getRegistries());
}
if (monitor == null) {
setMonitor(application.getMonitor());
}
}
}
- 获取配置中心 startConfigCenter
这个是 dubbo 2.7 以后新增的特性。新增了配置中心,应该始终先启动。
void startConfigCenter() {
// 配置中心是否存在
if (configCenter == null) {
ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc);
}
if (this.configCenter != null) {
// TODO there may have duplicate refresh
this.configCenter.refresh();
// 准备环境
prepareEnvironment();
}
ConfigManager.getInstance().refreshAll();
}
- checkDefault
检测 provider 是否存在,如果不存在则用系统变量创建一个。
private void checkDefault() {
createProviderIfAbsent();
}
private void createProviderIfAbsent() {
if (provider != null) {
return;
}
setProvider(
ConfigManager.getInstance()
.getDefaultProvider()
.orElseGet(() -> {
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.refresh();
return providerConfig;
})
);
}
- checkProtocol
检查协议,如果不存在则创建一个。可以通过 dubbo:protocol
和 dubbo:provider
和 dubbo:service
定义协议。
private void checkProtocol() {
if (CollectionUtils.isEmpty(protocols) && provider != null) {
setProtocols(provider.getProtocols());
}
convertProtocolIdsToProtocols();
}
private void convertProtocolIdsToProtocols() {
if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) {
// 从环境变量加载 protocol
List<String> configedProtocols = new ArrayList<>();
configedProtocols.addAll(getSubProperties(Environment.getInstance()
.getExternalConfigurationMap(), PROTOCOLS_SUFFIX));
configedProtocols.addAll(getSubProperties(Environment.getInstance()
.getAppExternalConfigurationMap(), PROTOCOLS_SUFFIX));
protocolIds = String.join(",", configedProtocols);
}
if (StringUtils.isEmpty(protocolIds)) {
if (CollectionUtils.isEmpty(protocols)) {
setProtocols(
ConfigManager.getInstance().getDefaultProtocols()
.filter(CollectionUtils::isNotEmpty)
.orElseGet(() -> {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.refresh();
return new ArrayList<>(Arrays.asList(protocolConfig));
})
);
}
} else {
String[] arr = COMMA_SPLIT_PATTERN.split(protocolIds);
List<ProtocolConfig> tmpProtocols = CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>();
Arrays.stream(arr).forEach(id -> {
if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setId(id);
protocolConfig.refresh();
return protocolConfig;
}));
}
});
if (tmpProtocols.size() > arr.length) {
throw new IllegalStateException("Too much protocols found, the protocols comply to this service are :" + protocolIds + " but got " + protocols
.size() + " registries!");
}
setProtocols(tmpProtocols);
}
}
- checkApplication
检查应用信息,即 dubbo:application 定义的内容。
protected void checkApplication() {
// for backward compatibility
// 不存在则创建
createApplicationIfAbsent();
// 判断是否有效,需要包含 name
if (!application.isValid()) {
throw new IllegalStateException("No application config found or it's not a valid config! " +
"Please add <dubbo:application name=\"...\" /> to your spring config.");
}
ApplicationModel.setApplication(application.getName());
// backward compatibility
String wait = ConfigUtils.getProperty(SHUTDOWN_WAIT_KEY);
if (wait != null && wait.trim().length() > 0) {
System.setProperty(SHUTDOWN_WAIT_KEY, wait.trim());
} else {
wait = ConfigUtils.getProperty(SHUTDOWN_WAIT_SECONDS_KEY);
if (wait != null && wait.trim().length() > 0) {
System.setProperty(SHUTDOWN_WAIT_SECONDS_KEY, wait.trim());
}
}
}
- checkRegistry
如果 protocol 不是 injvm
, 即不是本地协议,需要检查注册中心。
protected void checkRegistry() {
loadRegistriesFromBackwardConfig();
convertRegistryIdsToRegistries();
for (RegistryConfig registryConfig : registries) {
if (!registryConfig.isValid()) {
throw new IllegalStateException("No registry config found or it's not a valid config! " +
"The registry config is: " + registryConfig);
}
}
useRegistryForConfigIfNecessary();
}