Dubbo的Consumer启动过程(URL对象的解析和生成)

我们从ReferenceConfig的init( ) 方法开始讲,ReferenceConfig代表着Dubbo的消费者的工厂对象,类似Spring的FactoryBean作用,可以通过它来获得Dubbo对业务对象封装的RPC对象。

public class ReferenceConfig<T> extends AbstractReferenceConfig {

    /**
     * Service 对象
     */
    private transient volatile T ref;

    public synchronized T get() {
        // 已销毁,不可获得
        if (destroyed) {
            throw new IllegalStateException("Already destroyed!");
        }
        // 初始化
        if (ref == null) {
            init();
        }
        return ref;
    }

    private void init() {
        // 已经初始化,直接返回
        if (initialized) {
            return;
        }
        initialized = true;
        // 校验接口名非空
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException(" interface not allow null!");
        }
        // 提取属性配置(环境变量 或 properties 属性)到 ConsumerConfig 对象
        // get consumer's global configuration
        checkDefault();
        // 提取属性配置(环境变量 + properties 属性)到 ReferenceConfig 对象
        appendProperties(this);
        // 若未设置 `generic` 属性,使用 `ConsumerConfig.generic` 属性。
        if (getGeneric() == null && getConsumer() != null) {
            setGeneric(getConsumer().getGeneric());
        }
        // 泛化接口的实现
        if (ProtocolUtils.isGeneric(getGeneric())) {
            interfaceClass = GenericService.class;
            // 普通接口的实现
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 校验接口和方法
            checkInterfaceAndMethods(interfaceClass, methods);
        }
        // 直连提供者,参见文档《直连提供者》https://dubbo.gitbooks.io/dubbo-user-book/demos/explicit-target.html
        // 【直连提供者】第一优先级,通过 -D 参数指定 ,例如 java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
        String resolve = System.getProperty(interfaceName);
        String resolveFile = null;
        // 【直连提供者】第二优先级,通过文件映射,例如 com.alibaba.xxx.XxxService=dubbo://localhost:20890
        if (resolve == null || resolve.length() == 0) {
            // 默认先加载,`${user.home}/dubbo-resolve.properties` 文件 ,无需配置
            resolveFile = System.getProperty("dubbo.resolve.file");
            if (resolveFile == null || resolveFile.length() == 0) {
                File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
                if (userResolveFile.exists()) {
                    resolveFile = userResolveFile.getAbsolutePath();
                }
            }
            // 存在 resolveFile ,则进行文件读取加载。
            if (resolveFile != null && resolveFile.length() > 0) {
                Properties properties = new Properties();
                FileInputStream fis = null;
                try {
                    fis = new FileInputStream(new File(resolveFile));
                    properties.load(fis);
                } catch (IOException e) {
                    throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
                } finally {
                    try {
                        if (null != fis) { fis.close(); }
                    } catch (IOException e) {
                        logger.warn(e.getMessage(), e);
                    }
                }
                resolve = properties.getProperty(interfaceName);
            }
        }
        // 设置直连提供者的 url
        if (resolve != null && resolve.length() > 0) {
            url = resolve;
            if (logger.isWarnEnabled()) {
                if (resolveFile != null && resolveFile.length() > 0) {
                    logger.warn(
                        "Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
                } else {
                    logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
                }
            }
        }
        // 从 ConsumerConfig 对象中,读取 application、module、registries、monitor 配置对象。
        if (consumer != null) {
            if (application == null) {
                application = consumer.getApplication();
            }
            if (module == null) {
                module = consumer.getModule();
            }
            if (registries == null) {
                registries = consumer.getRegistries();
            }
            if (monitor == null) {
                monitor = consumer.getMonitor();
            }
        }
        // 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        // 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        // 校验 ApplicationConfig 配置。
        checkApplication();
        // 校验 Stub 和 Mock 相关的配置
        checkStubAndMock(interfaceClass);
        // 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。
        Map map = new HashMap();
        Map attributes = new HashMap();
        map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        // methods、revision、interface
        if (!isGeneric()) {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }

            // 将接口的方法名称存入参数集合中
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); // 获得方法数组
            if (methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            } else {
                map.put("methods", StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
            }
        }
        map.put(Constants.INTERFACE_KEY, interfaceName);
        // 将各种配置对象,添加到 `map` 集合中。
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, consumer, Constants.DEFAULT_KEY);
        appendParameters(map, this);
        // 获得服务键,作为前缀
        String prefix = StringUtils.getServiceKey(map);
        // 将 MethodConfig 对象数组,添加到 `map` 集合中。
        if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                // 将 MethodConfig 对象,添加到 `map` 集合中。
                appendParameters(map, method, method.getName());
                // 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                // 将带有 @Parameter(attribute = true) 配置对象的属性,添加到参数集合。参见《事件通知》http://dubbo.io/books/dubbo-user-book/demos/events-notify.html
                appendAttributes(attributes, method, prefix + "." + method.getName());
                // 检查属性集合中的事件通知方法是否正确。若正确,进行转换。
                checkAndConvertImplicitConfig(method, map, attributes);
            }
        }

        // 以系统环境变量( DUBBO_IP_TO_REGISTRY ) 作为服务注册地址,参见 https://github.com/dubbo/dubbo-docker-sample 项目。
        String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
        if (hostToRegistry == null || hostToRegistry.length() == 0) {
            hostToRegistry = NetUtils.getLocalHost();
        } else if (isInvalidLocalHost(hostToRegistry)) {
            throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
        }
        map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

        // 添加到 StaticContext 进行缓存
        //attributes are stored by system context.
        StaticContext.getSystemContext().putAll(attributes);

        // 创建 Service 代理对象
        ref = createProxy(map);

        ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
        ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
    }

    /**
     * 创建 Service 代理对象
     *
     * @param map 集合
     * @return 代理对象
     */
    @SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
    private T createProxy(Map map) {
        URL tmpUrl = new URL("temp", "localhost", 0, map);
        // 是否本地引用
        final boolean isJvmRefer;
        // injvm 属性为空,不通过该属性判断
        if (isInjvm() == null) {
           ......
        }
        // 本地引用
        if (isJvmRefer) {
            ...
        } else {
            // 定义直连地址,可以是服务提供者的地址,也可以是注册中心的地址
            if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
               ......
            } else {

                // assemble URL from register center's configuration
                // 1. ################加载注册中心 URL 数组,如果指定了多个注册中心,那么就会返回多个URL,每个url包含当前对象的参数  
                List us = loadRegistries(false);

                // 循环数组,添加到 `url` 中。
                if (us != null && !us.isEmpty()) {
                    for (URL u : us) {
                        // 加载监控中心 URL
                        URL monitorUrl = loadMonitor(u);
                        // 服务引用配置对象 `map`,带上监控中心的 URL
                        if (monitorUrl != null) {
                            map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                        }
                        // 注册中心的地址,带上服务引用的配置参数
                        urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); // 注册中心,带上服务引用的配置参数
                    }
                }
                if (urls == null || urls.isEmpty()) {
                    throw new IllegalStateException(
                        "No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version
                            .getVersion() + ", please config  to your spring config.");
                }
            }

            // 单 `urls` 时,引用服务,返回 Invoker 对象
            if (urls.size() == 1) {

                //###################### 2. 引用服务
                invoker = refprotocol.refer(interfaceClass, urls.get(0));

            } else {
                // 循环 `urls` ,引用服务,返回 Invoker 对象
                List> invokers = new ArrayList>();
                URL registryURL = null;
                for (URL url : urls) {
                    // 引用服务
                    invokers.add(refprotocol.refer(interfaceClass, url));
                    // 使用最后一个注册中心的 URL
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // use last registry url
                    }
                }
                // 有注册中心
                if (registryURL != null) { // registry url is available
                    // 对有注册中心的 Cluster 只用 AvailableCluster
                    // use AvailableCluster only when register's cluster is available
                    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                    // 将多个invoker组合成一个集群式的invker,以便负载均衡
                    invoker = cluster.join(new StaticDirectory(u, invokers));
                    // 无注册中心
                } else { // not a registry url
                    // // 将多个invoker组合成一个集群式的invker,以便负载均衡
                    invoker = cluster.join(new StaticDirectory(invokers));
                }
            }
        }

        // 启动时检查
        Boolean c = check;
        if (c == null && consumer != null) {
            c = consumer.isCheck();
        }
        if (c == null) {
            c = true; // default true
        }
        // 如果check=true,且@Reference标识的接口没有Provider, invoker对象一般为MockClusterInvoker类型
        if (c && !invoker.isAvailable()) {
            throw new IllegalStateException(
                "Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/")
                    + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils
                    .getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }

        // 创建 Service 代理对象
        // create service proxy
        return (T)proxyFactory.getProxy(invoker);
    }


}

在createProxy(Map< String, String > map) 方法里完成消费者的创建过程,返回给应用容器。

这里面有两步最关键:

  1. 生成URL对象。
  2. 通过URL对象的内容,向注册中心注册,同时订阅Provider,然后根据订阅返回的内容,连接Provider。

假设我们的消费者配置形式如下:

dubbo.application.name=consumer
dubbo.registry.address=zookeeper://lanboal:123456@127.0.0.1:2181?login=false

我们先看第一步(上文代码上标识了:###########1),如何生成URL对象:

public class ReferenceConfig<T> extends AbstractReferenceConfig {

    /**
     * 加载注册中心 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) {
                // 获得注册中心的地址,格式为:zookeeper://127.0.0.1:2181
                String address = config.getAddress();
                if (address == null || address.length() == 0) {
                    address = Constants.ANYHOST_VALUE;
                }
                String sysaddress = System.getProperty("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 数组。(数组大小可以为一),同时将map内的参数填充入每个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向zookeeper注册自身
                        url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                        // 添加到结果
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true)) // 服务提供者 && 注册
                            || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) { // 服务消费者 && 订阅
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

}

上段代码中,有一行 : UrlUtils.parseURLs(address, map) ,这一行的作用就是将字符串形式的url,参数,解析封装成一个URL对象:

public class UrlUtils {

     /**
     * 解析多个 URL ,将 `defaults` 里的参数,合并到 `address` 中。
     *
     * 合并的逻辑如下:
     *
     * 我们可以把 `address` 认为是 url ;`defaults` 认为是 defaultURL 。
     * 若 url 有不存在的属性时,从 defaultURL 获得对应的属性,设置到 url 中。
     *
     * @param address  地址
     * @param defaults 默认参数集合
     * @return URL
     */
    public static List parseURLs(String address, Map defaults) {
        if (address == null || address.length() == 0) {
            return null;
        }
        // 拆分注册中心地址,按照逗号或者分号。
        String[] addresses = Constants.REGISTRY_SPLIT_PATTERN.split(address);
        if (addresses == null || addresses.length == 0) {
            return null; //here won't be empty
        }
        List registries = new ArrayList();
        for (String addr : addresses) {
            // 解析 URL ,将 `defaults` 里的参数,合并到 `addr` 中。
            registries.add(parseURL(addr, defaults));
        }
        return registries;
    }


    /**
     * 解析单个 URL ,将 `defaults` 里的参数,合并到 `address` 中。
     *
     * 合并的逻辑如下:
     *
     * 我们可以把 `address` 认为是 url ;`defaults` 认为是 defaultURL 。
     * 若 url 有不存在的属性时,从 defaultURL 获得对应的属性,设置到 url 中。
     *
     * @param address  地址
     * @param defaults 默认参数集合
     * @return URL
     */
    public static URL parseURL(String address, Map defaults) {
        if (address == null || address.length() == 0) {
            return null;
        }
        // 以 Zookeeper 注册中心,配置集群的例子如下:
        // java config : dubbo.registry.address = zookeeper://lanboal:[email protected]:2181?login=false
        // 第一种,
        // 第二种,
        String url;
        if (address.contains("://")) { // 第一种
            url = address;
        } else { // 第二种
            String[] addresses = Constants.COMMA_SPLIT_PATTERN.split(address); // 按照 逗号 拆分
            url = addresses[0];
            if (addresses.length > 1) {
                StringBuilder backup = new StringBuilder();
                for (int i = 1; i < addresses.length; i++) {
                    if (i > 1) {
                        backup.append(",");
                    }
                    backup.append(addresses[i]);
                }
                url += "?" + Constants.BACKUP_KEY + "=" + backup.toString();
            }
        }
        // 从 `defaults` 中,获得 "protocol" "username" "password" "host" "port" "path" 到 `defaultXXX` 属性种。
        // 因为,在 Dubbo URL 中,这几个是独立的属性,不在 `Dubbo.parameters` 属性中。
        String defaultProtocol = defaults == null ? null : defaults.get("protocol");
        if (defaultProtocol == null || defaultProtocol.length() == 0) { // 如果地址没有协议缺省为 dubbo
            defaultProtocol = "dubbo";
        }
        String defaultUsername = defaults == null ? null : defaults.get("username");
        String defaultPassword = defaults == null ? null : defaults.get("password");
        int defaultPort = StringUtils.parseInteger(defaults == null ? null : defaults.get("port"));
        String defaultPath = defaults == null ? null : defaults.get("path");
        Map defaultParameters = defaults == null ? null : new HashMap(defaults);
        if (defaultParameters != null) { // 需要移除,因为这几个是独立属性。
            defaultParameters.remove("protocol");
            defaultParameters.remove("username");
            defaultParameters.remove("password");
            defaultParameters.remove("host");
            defaultParameters.remove("port");
            defaultParameters.remove("path");
        }
        // #################### 创建 Dubbo URL 。
        URL u = URL.valueOf(url);
        // 若 `u` 的属性存在非空的情况下,从 `defaultXXX` 属性,赋值到 `u` 的属性中。
        boolean changed = false; // 是否改变,即从 `defaultXXX` 属性,赋值到 `u` 的属性中。
        String protocol = u.getProtocol();
        String username = u.getUsername();
        String password = u.getPassword();
        String host = u.getHost();
        int port = u.getPort();
        String path = u.getPath();
        Map parameters = new HashMap(u.getParameters());

        // 如果原始url上未指定protocol,username,password, 但有default值,则追加到url上
        if ((protocol == null || protocol.length() == 0) && defaultProtocol.length() > 0) {
            changed = true;
            protocol = defaultProtocol;
        }
        if ((username == null || username.length() == 0) && defaultUsername != null && defaultUsername.length() > 0) {
            changed = true;
            username = defaultUsername;
        }
        if ((password == null || password.length() == 0) && defaultPassword != null && defaultPassword.length() > 0) {
            changed = true;
            password = defaultPassword;
        }
        /*if (u.isAnyHost() || u.isLocalHost()) {
            changed = true;
            host = NetUtils.getLocalHost();
        }*/
        if (port <= 0) {
            if (defaultPort > 0) {
                changed = true;
                port = defaultPort;
            } else { // 如果地址没有端口缺省为9090。FROM http://dubbo.io/books/dubbo-user-book/references/xml/dubbo-registry.html 文档。
                changed = true;
                port = 9090;
            }
        }
        if (path == null || path.length() == 0) {
            if (defaultPath != null && defaultPath.length() > 0) {
                changed = true;
                path = defaultPath;
            }
        }
        if (defaultParameters != null && defaultParameters.size() > 0) {
            for (Map.Entry entry : defaultParameters.entrySet()) {
                String key = entry.getKey();
                String defaultValue = entry.getValue();
                if (defaultValue != null && defaultValue.length() > 0) {
                    String value = parameters.get(key);
                    if (value == null || value.length() == 0) {
                        changed = true;
                        parameters.put(key, defaultValue);
                    }
                }
            }
        }
        // 若改变,创建新的 Dubbo URL 。
        if (changed) {
            u = new URL(protocol, username, password, host, port, path, parameters);
        }
        return u;
    }

}

上段代码中有一行 : URL u = URL.valueOf(url); 这个函数的作用就是才是通过String字符串生成URL对象,其他的仅仅是完善URL对象内的参数。

public final class URL implements Serializable {

    /**
     * Parse url string
     * 解析 dubbo.registry.address=zookeeper://lanboal:[email protected]:2181?sercicecheck=false 指定的值
     *
     * @param url URL string
     * @return URL instance
     * @see URL
     */
    public static URL valueOf(String url) {
        if (url == null || (url = url.trim()).length() == 0) {
            throw new IllegalArgumentException("url == null");
        }
        String protocol = null;
        String username = null;
        String password = null;
        String host = null;
        int port = 0;
        String path = null;
        Map parameters = null;
        int i = url.indexOf("?"); // seperator between body and parameters
        if (i >= 0) {                                   // 如果存在参数,那么提取参数 sercicecheck=false
            String[] parts = url.substring(i + 1).split("\\&");
            parameters = new HashMap();
            for (String part : parts) {
                part = part.trim();
                if (part.length() > 0) {
                    int j = part.indexOf('=');
                    if (j >= 0) {
                        parameters.put(part.substring(0, j), part.substring(j + 1));
                    } else {
                        parameters.put(part, part);
                    }
                }
            }
            url = url.substring(0, i);                   // 截取url,抹去参数,也就是 ? 之后的数据
        }
        i = url.indexOf("://");
        if (i >= 0) {
            if (i == 0) { throw new IllegalStateException("url missing protocol: \"" + url + "\""); }
            protocol = url.substring(0, i);  // 提取协议 : zookeeper
            url = url.substring(i + 3);      // 截取协议之后的剩余数据,也就是 :// 之后,?之前的数据 zookeeper://lanboal:[email protected]:2181 >>  lanboal:[email protected]:2181
        } else {
            // case: file:/path/to/file.txt
            i = url.indexOf(":/");
            if (i >= 0) {
                if (i == 0) { throw new IllegalStateException("url missing protocol: \"" + url + "\""); }
                protocol = url.substring(0, i);
                url = url.substring(i + 1);
            }
        }

        i = url.indexOf("/");
        if (i >= 0) {
            path = url.substring(i + 1);
            url = url.substring(0, i);
        }
        // 提取用户名和密码
        i = url.indexOf("@");
        if (i >= 0) {
            username = url.substring(0, i);
            int j = username.indexOf(":");
            if (j >= 0) {
                username = username.substring(0, j);            // 用户名: lanboal
                password = username.substring(j + 1);           // 密码 : 123456
            }
            url = url.substring(i + 1);                         // 截取url : 127.0.0.1:2181
        }
        i = url.indexOf(":");
        if (i >= 0 && i < url.length() - 1) {
            port = Integer.parseInt(url.substring(i + 1));      // 端口 : 2181
            url = url.substring(0, i);                          // url :127.0.0.1
        }
        if (url.length() > 0) {
            host = url;
        }
        // new URL("zookeeper", "lanboal", "123456", "127.0.0.1", "2181", null, sercicecheck=false)
        return new URL(protocol, username, password, host, port, path, parameters);
    }

}

你可能感兴趣的:(dubbo)