精尽 Dubbo 源码分析 —— 服务暴露(一)之本地暴露(Injvm)

1.概述

Dubbo 服务暴露有两种方式

本地暴露,JVM 本地调用。配置如下:

在不配置 scope 的情况下,默认两种方式都暴露。

2.doExportUrls

本地暴露服务的顺序图如下:

我们看到 ServiceConfig#export() 方法中,会在配置初始化完成后,调用顺序图的起点 #doExportUrls() 方法,开始暴露服务。代码如下:

    /**
     * 暴露 Dubbo URL
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void doExportUrls() {
        // 加载注册中心 URL 数组
        List registryURLs = loadRegistries(true);
        // 循环 `protocols` ,向逐个注册中心分组暴露服务。
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

2.1 loadRegistries

加载注册中心 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;
    }

2.2 doExportUrlsFor1Protocol

基于单个协议,暴露服务。

 /**
     * 基于单个协议,暴露服务
     *
     * @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);
            }

            // 服务远程暴露
       省略。。。
}
}
2.2.1 exportLocal

#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");
        }
    }

3. Protocol

涉及的 Protocol 类图如下:
精尽 Dubbo 源码分析 —— 服务暴露(一)之本地暴露(Injvm)_第1张图片

3.1 AbstractProtocol

实现 Protocol 接口,协议抽象类。代码如下:

public abstract class AbstractProtocol implements Protocol {

    /**
     * Exporter 集合
     *
     * key: 服务键 {@link #serviceKey(URL)} 或 {@link URL#getServiceKey()} 。
     *      不同协议会不同
     */
    protected final Map> exporterMap = new ConcurrentHashMap>();
    
    // ... 省略和本文无关的方法与属性
    
}

3.2 InjvmProtocol

实现 AbstractProtocol 抽象类,Injvm 协议实现类。

3.2.1 属性
    /**
     * 协议名
     */
    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;
    }
3.2.2 export
public  Exporter export(Invoker invoker) throws RpcException {
    return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

创建 InjvmExporter 对象。

你可能感兴趣的:(dubbo源码解析)