文章

API配置(二)之服务提供者

本文基于 Dubbo 2.6.1 版本,望知悉。

友情提示,【配置】这块的内容,会相对比较枯燥。所以,如果看到一些很难懂的地方,建议先跳过。

对于 Dubbo ,重点是要去理解,多协议、RPC、容错等等模块,而不是【配置】。

估计好多胖友被【配置】这章劝退了把???

1. 概述

本文接 《API 配置(一)之应用》 ,分享服务提供者相关的配置:包括 provider-config 和 sub-config 部分。

配置类关系

  • 黄框 部分,provider-side
  • 其他 部分,sub-config

还是老样子,我们先来看一段 《Dubbo 用户指南 —— API 配置》 ,服务提供者的初始化代码:

1
// 服务实现XxxService xxxService = new XxxServiceImpl();// 当前应用配置ApplicationConfig application = new ApplicationConfig();application.setName("xxx");// 连接注册中心配置RegistryConfig registry = new RegistryConfig();registry.setAddress("10.20.130.230:9090");registry.setUsername("aaa");registry.setPassword("bbb");// 服务提供者协议配置ProtocolConfig protocol = new ProtocolConfig();protocol.setName("dubbo");protocol.setPort(12345);protocol.setThreads(200);// 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口// 服务提供者暴露服务配置ServiceConfig<XxxService> service = new ServiceConfig<XxxService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏service.setApplication(application);service.setRegistry(registry); // 多个注册中心可以用setRegistries()service.setProtocol(protocol); // 多个协议可以用setProtocols()service.setInterface(XxxService.class);service.setRef(xxxService);service.setVersion("1.0.0");// 暴露及注册服务service.export();
  • 相比 ReferenceConfig 的初始化,会多创建 ProtocolConfig 对象,设置到 ServiceConfig 对象中。

友情提示:本文前面部分会比较琐碎,重点在 「8. ServiceConfig」 部分。

2. ProtocolConfig

com.alibaba.dubbo.config.ProtocolConfig ,服务提供者协议配置。

3. AbstractMethodConfig

com.alibaba.dubbo.config.AbstractMethodConfig ,方法级配置的抽象类。

4. MethodConfig

com.alibaba.dubbo.config.MethodConfig ,继承 AbstractMethodConfig ,方法级配置。

5. AbstractInterfaceConfig

com.alibaba.dubbo.config.AbstractInterfaceConfig ,继承 AbstractMethodConfig ,抽象接口配置类。

6. AbstractServiceConfig

com.alibaba.dubbo.config.AbstractServiceConfig ,实现 AbstractInterfaceConfig ,抽象服务配置类。

7. ProviderConfig

com.alibaba.dubbo.config.ProviderConfig ,实现 AbstractServiceConfig ,服务提供者缺省值配置。

8. ServiceConfig

com.alibaba.dubbo.config.ServiceConfig ,服务提供者暴露服务配置类

下面,我们进入正戏

在文初的 ServiceConfig 的初始化示例代码中,最后调用的是 ServiceConfig#export() 方法。从方法的命名,我们可以看出,暴露服务。该方法主要做了如下几件事情:

  1. 进一步初始化 ServiceConfig 对象。
  2. 校验 ServiceConfig 对象的配置项。
  3. 使用 ServiceConfig 对象,生成数组 Dubbo URL 对象 。
  4. 使用 Dubbo URL 对象,暴露服务

本文重点在服务提供者相关的配置,因此只解析 1+2+3 部分( 不包括 4 )。代码如下:

1
public synchronized void export() {    // 当 export 或者 delay 未配置,从 ProviderConfig 对象读取。    if (provider != null) {        if (export == null) {            export = provider.getExport();        }        if (delay == null) {            delay = provider.getDelay();        }    }    // 不暴露服务( export = false ) ,则不进行暴露服务逻辑。    if (export != null && !export) {        return;    }    // 延迟暴露    if (delay != null && delay > 0) {        delayExportExecutor.schedule(new Runnable() {            public void run() {                doExport();            }        }, delay, TimeUnit.MILLISECONDS);        // 立即暴露    } else {        doExport();    }}
  • 第 2 至 10 行:当 exportdelay 或 未配置时,从 ProviderConfig 对象读取。
  • 第 11 至 14 行:当配置不需要暴露服务( export = false )时,直接返回。
  • 第 17 至 22 行:当配置延迟暴露( delayExportExecutor延迟 delay > 0 )时,使用 调度,调用 #doExport() 方法。
  • 第 23 至 26 行:立即暴露,调用 #doExport() 方法。

#doExport() 方法,代码如下:

1
protected synchronized void doExport() {    // 检查是否可以暴露,若可以,标记已经暴露。    if (unexported) {        throw new IllegalStateException("Already unexported!");    }    if (exported) {        return;    }    exported = true;    // 校验接口名非空    if (interfaceName == null || interfaceName.length() == 0) {        throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");    }    // 拼接属性配置(环境变量 + properties 属性)到 ProviderConfig 对象    checkDefault();    // 从 ProviderConfig 对象中,读取 application、module、registries、monitor、protocols 配置对象。    if (provider != null) {        if (application == null) {            application = provider.getApplication();        }        if (module == null) {            module = provider.getModule();        }        if (registries == null) {            registries = provider.getRegistries();        }        if (monitor == null) {            monitor = provider.getMonitor();        }        if (protocols == null) {            protocols = provider.getProtocols();        }    }    // 从 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();        }    }    // 泛化接口的实现    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);        // 校验指向的 service 对象        checkRef();        generic = Boolean.FALSE.toString();    }    // 处理服务接口客户端本地代理( `local` )相关。实际目前已经废弃,使用 `stub` 属性,参见 `AbstractInterfaceConfig#setLocal` 方法。    if (local != null) {        // 设为 true,表示使用缺省代理类名,即:接口名 + Local 后缀        if ("true".equals(local)) {            local = interfaceName + "Local";        }        Class<?> localClass;        try {            localClass = ClassHelper.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);        }    }    // 处理服务接口客户端本地代理( `stub` )相关    if (stub != null) {        // 设为 true,表示使用缺省代理类名,即:接口名 + Stub 后缀        if ("true".equals(stub)) {            stub = interfaceName + "Stub";        }        Class<?> stubClass;        try {            stubClass = ClassHelper.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);        }    }    // 校验 ApplicationConfig 配置。    checkApplication();    // 校验 RegistryConfig 配置。    checkRegistry();    // 校验 ProtocolConfig 配置数组。    checkProtocol();    // 读取环境变量和 properties 配置到 ServiceConfig 对象。    appendProperties(this);    // 校验 Stub 和 Mock 相关的配置    checkStubAndMock(interfaceClass);    // 服务路径,缺省为接口名    if (path == null || path.length() == 0) {        path = interfaceName;    }    // 暴露服务    doExportUrls();    // TODO 芋艿,等待 qos    ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);    ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);}
  • 第 2 至 9 行:检查是否可以暴露。若可以,标记已经暴露( exported = true )。
  • 第 10 至 13 行:校验接口名 interfaceName 非空。
  • 第 15 行:调用 #checkDefault()属性配置 方法,读取 ( 环境变量 + properties 属性 )到 ProviderConfig 对象。
  • 第 16 至 33 行:从 ProviderConfig 对象中,读取 applicationmoduleregistriesmonitorprotocols 、 、 、 、 对象。
  • 第 34 至 42 行:从 ModuleConfig 对象中,读取 registriesmonitor 、 对象。
  • 第 43 至 51 行:从 ApplicationConfig 对象中,读取 registriesmonitor 、 对象。
  • 第 52 至 57 行:泛化接口的实现。
  • 第 58 至 70 行:普通接口的实现。
    • 第 60 至 64 行:根据 interfaceName接口类interfaceClass ,获得对应的 ,并赋值给 。
    • 第 66 行:调用 #checkInterfaceAndMethods(interfaceClass, methods) 方法,检查接口和方法。
      • 本文有已经有这个方法的解析。
    • 第 68 行:调用 #checkRef() 方法,校验指向的 Service 对象。
    • 第 69 行:标记 generic 为 泛化实现。
  • 第 71 至 86 行:处理服务接口客户端本地代理( local实际目前已经废弃,此处主要用于兼容stubAbstractInterfaceConfig#setLocal(local)注释说明 )相关。 ,使用 属性,参见 方法的 。
  • 第 87 至 102 行:处理服务接口客户端本地代理( stubAbstractInterfaceConfig#setLocal(local) 属性,参见 )相关。
  • 第 104 行:调用 #checkApplication() 方法,校验 ApplicationConfig 配置。
    • 直接点击方法查看,较为简单,已经添加详细注释。
  • 第 106 行:调用 #checkRegistry() 方法,校验 RegistryConfig 配置。
    • 直接点击方法查看,较为简单,已经添加详细注释。
  • 第 108 行:调用 #checkProtocol() 方法,校验 ProtocolConfig 配置数组。
    • 直接点击方法查看,较为简单,已经添加详细注释。
  • 第 110 行:调用 #appendProperties(config)属性配置自己 方法,读取 ( 环境变量 + properties 属性 )到 ServiceConfig 对象( )。
  • 第 112 行:调用 #checkStubAndMock(interfaceClass) 方法,校验 Stub 和 Mock 相关的配置。
  • 第 113 至 116 行:服务路径 path 为空时,缺省为接口名。
  • 第 118 行:调用 #doExportUrls()暴露服务3+4 方法, 。此方法包含了我们上述的 部分。
  • 第 119 至 121 行:// TODO 芋艿,等待 qos

因为本文不分享 4 部分,所以下面我们只看 #doExportUrls() 方法中,调用 #doExportUrlsFor1Protocol(protocolConfig, registryURLs) 方法,和 3 有关的部分。代码如下:

1
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {    // 协议名    String name = protocolConfig.getName();    if (name == null || name.length() == 0) {        name = "dubbo";    }    // 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。    Map<String, String> map = new HashMap<String, String>();    map.put(Constants.SIDE_KEY, Constants.PROVIDER_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()));    }    // 将各种配置对象,添加到 `map` 集合中。    appendParameters(map, application);    appendParameters(map, module);    appendParameters(map, provider, Constants.DEFAULT_KEY); // ProviderConfig ,为 ServiceConfig 的默认属性,因此添加 `default` 属性前缀。    appendParameters(map, protocolConfig);    appendParameters(map, this);    // 将 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");                }            }            // 将 ArgumentConfig 对象数组,添加到 `map` 集合中。            List<ArgumentConfig> arguments = method.getArguments();            if (arguments != null && !arguments.isEmpty()) {                for (ArgumentConfig argument : arguments) {                    // convert argument type                    if (argument.getType() != null && argument.getType().length() > 0) { // 指定了类型                        Method[] methods = interfaceClass.getMethods();                        // visit all methods                        if (methods != null && methods.length > 0) {                            for (int i = 0; i < methods.length; i++) {                                String methodName = methods[i].getName();                                // target the method, and get its signature                                if (methodName.equals(method.getName())) { // 找到指定方法                                    Class<?>[] argTypes = methods[i].getParameterTypes();                                    // one callback in the method                                    if (argument.getIndex() != -1) { // 指定单个参数的位置 + 类型                                        if (argTypes[argument.getIndex()].getName().equals(argument.getType())) {                                            // 将 ArgumentConfig 对象,添加到 `map` 集合中。                                            appendParameters(map, argument, method.getName() + "." + argument.getIndex()); // `${methodName}.${index}`                                        } else {                                            throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());                                        }                                    } else {                                        // multiple callbacks in the method                                        for (int j = 0; j < argTypes.length; j++) {                                            Class<?> argClazz = argTypes[j];                                            if (argClazz.getName().equals(argument.getType())) {                                                // 将 ArgumentConfig 对象,添加到 `map` 集合中。                                                appendParameters(map, argument, method.getName() + "." + j); // `${methodName}.${index}`                                                if (argument.getIndex() != -1 && argument.getIndex() != j) { // 多余的判断,因为 `argument.getIndex() == -1` 。                                                    throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());                                                }                                            }                                        }                                    }                                }                            }                        }                    } else if (argument.getIndex() != -1) { // 指定单个参数的位置                        // 将 ArgumentConfig 对象,添加到 `map` 集合中。                        appendParameters(map, argument, method.getName() + "." + argument.getIndex()); // `${methodName}.${index}`                    } else {                        throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");                    }                }            }        } // end of methods for    }    // generic、methods、revision    if (ProtocolUtils.isGeneric(generic)) {        map.put("generic", generic);        map.put("methods", Constants.ANY_VALUE);    } else {        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<String>(Arrays.asList(methods)), ","));        }    }    // token ,参见《令牌校验》http://dubbo.apache.org/zh-cn/docs/user/demos/token-authorization.html    if (!ConfigUtils.isEmpty(token)) {        if (ConfigUtils.isDefault(token)) {            map.put("token", UUID.randomUUID().toString());        } else {            map.put("token", token);        }    }    // 协议为 injvm 时,不注册,不通知。    if ("injvm".equals(protocolConfig.getName())) {        protocolConfig.setRegister(false);        map.put("notify", "false");    }    // export service    String contextPath = protocolConfig.getContextpath();    if ((contextPath == null || contextPath.length() == 0) && provider != null) {        contextPath = provider.getContextpath();    }    // host、port    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);    Integer port = this.findConfigedPorts(protocolConfig, name, map);    // 创建 Dubbo URL 对象    URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);    // 配置规则,参见《配置规则》http://dubbo.apache.org/zh-cn/docs/user/demos/config-rule.html    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)            .hasExtension(url.getProtocol())) {        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);    }    // 省略【服务暴露】逻辑}

9. 为什么继承???

我们以 ServiceConfig 和 ProviderConfig 来举例子,两者都继承 AbstractServiceConfig。从属性上,两者有相同的属性,例如 group / version 。同时,也存在着一些差异,例如 ServiceConfig.interfaceName / ProviderConfig.host 。

另外,我们在看看 ServiceConfig 和 MethodConfig ,两者都继承 AbstractMethodConfig。在 ServiceConfig 中,可以配置下属所有方法的 retries 次数,也可以在 MethodConfig 中自定义retries 次数。

通过继承,获得相同的属性。

10. Version

Version#getVersion(cls, defaultVersion) 方法,获得版本号。代码如下:

1
public static String getVersion(Class<?> cls, String defaultVersion) {    try {        // find version info from MANIFEST.MF first        String version = cls.getPackage().getImplementationVersion();        if (version == null || version.length() == 0) {            version = cls.getPackage().getSpecificationVersion();        }        if (version == null || version.length() == 0) {            // guess version fro jar file name if nothing's found from MANIFEST.MF            CodeSource codeSource = cls.getProtectionDomain().getCodeSource();            if (codeSource == null) {                logger.info("No codeSource for class " + cls.getName() + " when getVersion, use default version " + defaultVersion);            } else {                String file = codeSource.getLocation().getFile();                if (file != null && file.length() > 0 && file.endsWith(".jar")) {                    file = file.substring(0, file.length() - 4);                    int i = file.lastIndexOf('/');                    if (i >= 0) {                        file = file.substring(i + 1);                    }                    i = file.indexOf("-");                    if (i >= 0) {                        file = file.substring(i + 1);                    }                    while (file.length() > 0 && !Character.isDigit(file.charAt(0))) {                        i = file.indexOf("-");                        if (i >= 0) {                            file = file.substring(i + 1);                        } else {                            break;                        }                    }                    version = file;                }            }        }        // return default version if no version info is found        return version == null || version.length() == 0 ? defaultVersion : version;    } catch (Throwable e) {        // return default version when any exception is thrown        logger.error("return default version, ignore exception " + e.getMessage(), e);        return defaultVersion;    }}
  • 第 3 至 7 行:从 spring-boot-starter-1.5.10.RELEASE.jar MAINFEST.MF 中获得版本号。以 举例子: MAINFEST.MF
  • 第 8 至 36 行:若获取不到,从 jar 包命名可能 中 带的版本号作为结果。例如上面的例子, 1.5.10.RELEASE 。
  • 第 38 行:返回版本号。若不存在,返回默认版本号。
本文由作者按照 CC BY 4.0 进行授权