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 ,服务提供者协议配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:protocol》 文档。
3. AbstractMethodConfig
com.alibaba.dubbo.config.AbstractMethodConfig ,方法级配置的抽象类。
- 部分《Dubbo 用户指南 —— dubbo:method》 属性的解释,参见 文档。
4. MethodConfig
com.alibaba.dubbo.config.MethodConfig ,继承 AbstractMethodConfig ,方法级配置。
- 具体属性的解释,《Dubbo 用户指南 —— dubbo:method》 文档。
5. AbstractInterfaceConfig
com.alibaba.dubbo.config.AbstractInterfaceConfig ,继承 AbstractMethodConfig ,抽象接口配置类。
- 具体属性的解释,需要寻找《Dubbo 用户指南 —— dubbo:service》《Dubbo 用户指南 —— dubbo:reference》 在 或 文档。
- 下面的方法,会在 「8. ServiceConfig」 的初始化被调用,胖友可需要的时候,点击查看。
- #checkApplication()实际上 方法,校验 ApplicationConfig 配置。 ,该方法会初始化 ApplicationConfig 的配置属性。
- 直接点击方法查看,较为简单,已经添加详细注释。
- #checkRegistry()实际上 方法,校验 RegistryConfig 配置。 ,该方法会初始化 RegistryConfig 的配置属性。
- 直接点击方法查看,较为简单,已经添加详细注释。
- #checkInterfaceAndMethods(interfaceClass, methods) 方法,校验接口和方法。主要是两方面:
- 1、 接口类非空,并是接口
- 2、 方法在接口中已定义
- 直接点击方法查看,较为简单,已经添加详细注释。
- #checkStubAndMock(interfaceClass) 方法,校验 Stub 和 Mock 相关的配置。
- 以上未列举的 #loadRegistries(provider)#loadMonitor(registryURL)后续文章 和 方法,在 需要使用到时,在详细分享。
6. AbstractServiceConfig
com.alibaba.dubbo.config.AbstractServiceConfig ,实现 AbstractInterfaceConfig ,抽象服务配置类。
- 具体属性的解释,需要寻找《Dubbo 用户指南 —— dubbo:service》《Dubbo 用户指南 —— dubbo:provider》 在 或 文档。
7. ProviderConfig
com.alibaba.dubbo.config.ProviderConfig ,实现 AbstractServiceConfig ,服务提供者缺省值配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:provider》 文档。
8. ServiceConfig
com.alibaba.dubbo.config.ServiceConfig ,服务提供者暴露服务配置类。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:service》 文档。
下面,我们进入正戏。
在文初的 ServiceConfig 的初始化示例代码中,最后调用的是 ServiceConfig#export() 方法。从方法的命名,我们可以看出,暴露服务。该方法主要做了如下几件事情:
- 进一步初始化 ServiceConfig 对象。
- 校验 ServiceConfig 对象的配置项。
- 使用 ServiceConfig 对象,生成数组 Dubbo URL 对象 。
- 使用 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 对象。
- 关于“属性配置《精尽 Dubbo 源码解析 —— 属性配置》 ” ,在 详细解析。
- 直接点击方法查看,较为简单,已经添加详细注释。
- 第 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); } // 省略【服务暴露】逻辑}
- 第 2 至 6 行:协议名空时,缺省 “dubbo” 。
- 第 9 行:创建参数集合 parameters map ,用于下面创建 Dubbo URL 的 属性。
- 第 10 至 15 行:将 side dubbo timestamp timestamp pid 添加到 map 中。
- 第 16 至 21 行:调用 #appendParameters(map, config) 方法,将各种配置对象添加到 map 中。
- #appendParameters(map, config)《API 配置(一)之应用》 方法,在
- 第 22 至 83 行:调用 MethodConfig 对象数组 ,添加到 map 中。
- 目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到 map 中。
- 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。
- 第 85 至 102 行:将 generic methods revision 到 map 中。
- revision「10. Version」 ,可能比较难理解,在 详细解析。
- 第 103 至 110 行:将 token 添加到 map 中。
- 第 111 至 115 行:当协议为 injvm 时,添加 notify = false 到 map 中,表示不注册,不通知。
- 第 116 至 120 行:获得 contextPath ,基础路径,即java web应用中常说的context path 。
- 第 123 行:调用 #this.findConfigedHosts(protocolConfig, registryURLs, map) 方法,获得注册到注册中心的服务提供者 Host 。
- 《Dubbo 用户指南 —— 主机绑定》
- 《dubbo注册服务IP解析异常及IP解析源码分析》
- 指定服务注册地址,参见 dubbo-docker-sample 示例项目。
- 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。
- 第 124 行:调用 #findConfigedHosts(protocolConfig, name, map) 方法,获得注册到注册中心的服务提供者 Port 。
- 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。
- 第 127 行:创建 Dubbo URL 。
- 第 129 至 134 行:配置规则,后续详细解析。
- 第 136 行:省略 【服务暴露】逻辑。
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 行:返回版本号。若不存在,返回默认版本号。
