Dubbo通过Springboot自动装配原理,将ServiceBean类注入到Spring容器中,并通过ServiceBean对象来发布服务。
服务发布入口ServiceBean
这里的泛型T指的是各个需要被暴露出的接口类。通过DubboAutoConfiguration自动装配类里的ServiceAnnotationBeanPostProcessor去扫描基础路径并构建成一个个待暴露的ServiceBean类。
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware {
ServiceBean实现了Spring的事件监听器ApplicationListener并监听上下文刷新事件。当Spring容器启动完成后,会发送对应的上下文刷新事件。当每个ServiceBean接收到对应的上下文刷新事件时,就会执行对应的onApplicationEvent方法。
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// 服务暴露
export();
}
}
首先判断该服务是否已经完成服务暴露。没有,则调用ServiceConfig父类的export方法。
public synchronized void export() {
// 检查配置
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
在服务暴露之前,检查配置以及判断是否需要延迟服务暴露,最终通过doExport方法做真正的服务暴露操作。
我们进入doExport方法
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();
}
进入doExportUrls方法
private void doExportUrls() {
// 拼接配置的注册中心URL
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
// 标注服务已经导出
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
loadRegistries()方法实际上是拼接我们在项目中(bootstrap.yaml)等配置文件中配置了dubbo.register的配置信息。
结果例如:
registry://127.0.0.1:8848/org.apache.dubbo.registry.RegistryService?application=test-provider&dubbo=2.0.2&pid=27976&qos.enable=false®istry=nacos&release=2.7.3×tamp=1598156456239
附上loadRegistries方法
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
// 获取注册地址
// nacos://127.0.0.1:8848
String address = config.getAddress();
// 如果为空,则取0.0.0.0
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
// 如果没有指定服务调用协议类型,则默认为dubbo协议
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
// 将Map转换为URL的形式
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
回到doExportUrls方法中,进入doExportUrlsFor1Protocol方法。
ServiceConfig.doExportUrlsFor1Protocol
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
appendRuntimeParameters(map);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
if (CollectionUtils.isNotEmpty(methods)) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey