Dubbo 服务暴露有两种方式
本地暴露,JVM 本地调用。配置如下:
在不配置 scope 的情况下,默认两种方式都暴露。
本地暴露服务的顺序图如下:
我们看到 ServiceConfig#export() 方法中,会在配置初始化完成后,调用顺序图的起点 #doExportUrls() 方法,开始暴露服务。代码如下:
/**
* 暴露 Dubbo URL
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
// 加载注册中心 URL 数组
List registryURLs = loadRegistries(true);
// 循环 `protocols` ,向逐个注册中心分组暴露服务。
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
加载注册中心 com.alibaba.dubbo.common.URL 数组。代码如下
/**
* 加载注册中心 URL 数组
*
* @param provider 是否是服务提供者
* @return URL 数组
*/
protected List loadRegistries(boolean provider) {
// 校验 RegistryConfig 配置数组。
checkRegistry();
// 创建 注册中心 URL 数组
List registryList = new ArrayList();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
// 获得注册中心的地址
String address = config.getAddress();
if (address == null || address.length() == 0) {
address = Constants.ANYHOST_VALUE;
}
String sysaddress = System.getProperty("dubbo.registry.address"); // 从启动参数读取
//从启动参数 dubbo.registry.address 读取,若存在,最高优先级,进行覆盖
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
// 有效的地址
if (address.length() > 0
&& !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map map = new HashMap();
// 将各种配置对象,添加到 `map` 集合中。
appendParameters(map, application);
appendParameters(map, config);
// 添加 `path` `dubbo` `timestamp` `pid` 到 `map` 集合中。
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 若不存在 `protocol` 参数,默认 "dubbo" 添加到 `map` 集合中。
if (!map.containsKey("protocol")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) { // "remote"
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
// 解析地址,创建 Dubbo URL 数组。(数组大小可以为一)
List urls = UrlUtils.parseURLs(address, map);
// 循环 `url` ,设置 "registry" 和 "protocol" 属性。
for (URL url : urls) {
// 设置 `registry=${protocol}` 和 `protocol=registry` 到 URL
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
// 添加到结果
if ((provider && url.getParameter(Constants.REGISTER_KEY, true)) // 服务提供者 && 注册 https://dubbo.gitbooks.io/dubbo-user-book/demos/subscribe-only.html
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) { // 服务消费者 && 订阅 https://dubbo.gitbooks.io/dubbo-user-book/demos/registry-only.html
registryList.add(url);
}
}
}
}
}
return registryList;
}
基于单个协议,暴露服务。
/**
* 基于单个协议,暴露服务
*
* @param protocolConfig 协议配置对象
* @param registryURLs 注册中心链接对象数组
*/
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// 服务本地暴露
// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// 服务远程暴露
省略。。。
}
}
#exportLocal(url) 方法,本地暴露服务。代码如下:
/**
* 本地暴露服务
*
* @param url 注册中心 URL
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
// 创建本地 Dubbo URL
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL) // injvm
.setHost(LOCALHOST) // 本地
.setPort(0); // 端口=0
// 添加服务的真实类名,例如 DemoServiceImpl ,仅用于 RestProtocol 中。
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
// 使用 ProxyFactory 创建 Invoker 对象
// 使用 Protocol 暴露 Invoker 对象
Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local);
System.out.println(protocol);
Exporter exporter = protocol.export(invoker);
// 添加到 `exporters`
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
实现 Protocol 接口,协议抽象类。代码如下:
public abstract class AbstractProtocol implements Protocol {
/**
* Exporter 集合
*
* key: 服务键 {@link #serviceKey(URL)} 或 {@link URL#getServiceKey()} 。
* 不同协议会不同
*/
protected final Map> exporterMap = new ConcurrentHashMap>();
// ... 省略和本文无关的方法与属性
}
实现 AbstractProtocol 抽象类,Injvm 协议实现类。
/**
* 协议名
*/
public static final String NAME = Constants.LOCAL_PROTOCOL;
/**
* 默认端口
*/
public static final int DEFAULT_PORT = 0;
/**
* 单例。在 Dubbo SPI 中,被初始化,有且仅有一次。
*/
private static InjvmProtocol INSTANCE;
public InjvmProtocol() {
INSTANCE = this;
}
public static InjvmProtocol getInjvmProtocol() {
if (INSTANCE == null) {
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(InjvmProtocol.NAME); // load
}
return INSTANCE;
}
public Exporter export(Invoker invoker) throws RpcException {
return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
创建 InjvmExporter 对象。