API配置(一)之应用
本文基于 Dubbo 2.6.1 版本,望知悉。
友情提示,【配置】这块的内容,会相对比较枯燥。所以,如果看到一些很难懂的地方,建议先跳过。
对于 Dubbo ,重点是要去理解,多协议、RPC、容错等等模块,而不是【配置】。
估计好多胖友被【配置】这章劝退了把???
1. 概述
我们都“知道”,Dubbo 的配置是非常“灵活”的。
例如,目前提供了四种配置方式:
- API 配置
- 属性配置
- XML 配置
- 注解配置
ps: 后续的几篇文章也是按照这样的顺序,解析 Dubbo 配置的源码。
再例如,可灵活设置的配置项:
FROM 《Dubbo 用户指南 —— schema 配置参考手册》
所有配置项分为三大类,参见下表中的”作用”一列。
- 服务发现:表示该配置项用于服务的注册与发现,目的是让消费方找到提供方。
- 服务治理:表示该配置项用于治理服务间的关系,或为开发测试提供便利条件。
- 性能调优:表示该配置项用于调优性能,不同的选项对性能会产生影响。
所有配置最终都将转换为 Dubbo URL 表示,并由服务提供方生成,经注册中心传递给消费方,各属性对应 URL 的参数,参见配置项一览表中的 “对应URL参数” 列。
ps: 可能转换成 Dubbo URL 不太好理解。良心如笔者,后续有文章会贯串它。
当然,凡事都有两面性,在社区里也存在建议的声音,例如:《ISSUE#738:XML配置项重新梳理》 :
目前有一些配置项存在暴露的位置不正确、暴露不全面、文档和含义不匹配等问题,期望在2.5.7版本将已知问题予以整理修复
如果使用中有遇到的配置问题,请在评论中列出以便改进
2. 配置一览
我们来看看 dubbo-config-api 的项目结构,如下图所示:
dubbo-config-api 项目结构
一脸懵逼,好多啊。下面我们来整理下配置之间的关系,如下图所示:
配置之间的关系
从这张图中,可以看出分成四个部分:
- application-shared
- provider-side
- consumer-side
- sub-config
实际上,上图和目前版本的代码会存在一点点出入,我们在看看实际的类关系,如下图所示:
配置类关系
- 红勾 部分,application-shared ,在本文进行分享。
- 黄框《API 配置(二)之服务提供者》 部分,provider-side ,在 分享。
- 红框《API 配置(三)之服务消费者》 部分,consumer-side ,在 分享。
- 其他《API 配置(二)之服务提供者》 部分,sub-config ,在 分享。
3. Config
我们先来看一段 《Dubbo 用户指南 —— API 配置》 ,提供的消费者的初始化代码:
1
// 当前应用配置ApplicationConfig application = new ApplicationConfig();application.setName("yyy");// 连接注册中心配置RegistryConfig registry = new RegistryConfig();registry.setAddress("10.20.130.230:9090");registry.setUsername("aaa");registry.setPassword("bbb");// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接// 引用远程服务ReferenceConfig<XxxService> reference = new ReferenceConfig<XxxService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏reference.setApplication(application);reference.setRegistry(registry); // 多个注册中心可以用setRegistries()reference.setInterface(XxxService.class);reference.setVersion("1.0.0");// 和本地bean一样使用xxxServiceXxxService xxxService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
- 可以看到,创建了 ApplicationConfig 和 RegistryConfig 对象,设置到 ReferenceConfig 对象。
- 如果创建 ModuleConfig 或 MonitorConfig 对象,也是可以设置到 ReferenceConfig 对象中。
3.1 AbstractConfig
com.alibaba.dubbo.config.AbstractConfig ,抽象配置类,除了 ArgumentConfig ,我们可以看到所有的配置类都继承该类。
AbstractConfig 主要提供配置解析与校验相关的工具方法。下面我们开始看看它的代码。
id 属性,配置对象的编号,适用于除了 API 配置之外的三种配置方式,标记一个配置对象,可用于对象之间的引用。例如 XML 的 ,其中 provider 为 的 ID 属性。
那为什么说不适用 API 配置呢?直接 #setXXX(config) 对象即可。
配置项校验的工具方法,例如属性值长度限制、格式限制等等,比较简单。相关代码如下:
#appendParameters(parameters, config, prefix) 方法,将配置对象的属性,添加到参数集合。代码如下 :
在看具体代码之前,我们先来了解 「4. URL」 和 「5. @Parameter」 。
1
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) { if (config == null) { return; } Method[] methods = config.getClass().getMethods(); for (Method method : methods) { try { String name = method.getName(); if ((name.startsWith("get") || name.startsWith("is")) && !"getClass".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && isPrimitive(method.getReturnType())) { // 方法为获取基本类型,public 的 getting 方法。 Parameter parameter = method.getAnnotation(Parameter.class); if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) { continue; } // 获得属性名 int i = name.startsWith("get") ? 3 : 2; String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), "."); String key; if (parameter != null && parameter.key() != null && parameter.key().length() > 0) { key = parameter.key(); } else { key = prop; } // 获得属性值 Object value = method.invoke(config, new Object[0]); String str = String.valueOf(value).trim(); if (value != null && str.length() > 0) { // 转义 if (parameter != null && parameter.escaped()) { str = URL.encode(str); } // 拼接,详细说明参见 `Parameter#append()` 方法的说明。 if (parameter != null && parameter.append()) { String pre = parameters.get(Constants.DEFAULT_KEY + "." + key); // default. 里获取,适用于 ServiceConfig =》ProviderConfig 、ReferenceConfig =》ConsumerConfig 。 if (pre != null && pre.length() > 0) { str = pre + "," + str; } pre = parameters.get(key); // 通过 `parameters` 属性配置,例如 `AbstractMethodConfig.parameters` 。 if (pre != null && pre.length() > 0) { str = pre + "," + str; } } if (prefix != null && prefix.length() > 0) { key = prefix + "." + key; } parameters.put(key, str); // System.out.println("kv:" + key + "\t" + str); } else if (parameter != null && parameter.required()) { throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null"); } } else if ("getParameters".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && method.getReturnType() == Map.class) { // `#getParameters()` 方法 Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]); if (map != null && map.size() > 0) { String pre = (prefix != null && prefix.length() > 0 ? prefix + "." : ""); for (Map.Entry<String, String> entry : map.entrySet()) { parameters.put(pre + entry.getKey().replace('-', '.'), entry.getValue()); } } } } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } }}
- parameters ,参数集合。实际上,该集合会用于 URL.parameters 。
- config ,配置对象。
- prefix ,属性前缀。用于配置项添加到 parameters 中时的前缀。
- 第 5 行:获得所有方法的数组,为下面通过反射 获得配置项的值做准备。
- 第 6 行:循环 每个方法。
- 第 9 至 13 行:方法为获得基本类型 + public 的 getting 方法。
- 第 14 至 17 行:返回值类型为 Object 或排除( [
@Parameter.exclue](http://svip.iocoder.cn/Dubbo/configuration-api-1/mailto:%60@Parameter.exclue)<font style="color:rgb(51, 51, 51);">=true)的配置项,跳过。 - 第 19 至 26 行:获得配置项名 。
- 第 28 至 48 行:获得配置项值 。中间有一些逻辑处理,胖友看下代码的注释。
- 第 49 行:添加配置项到 parameters 。
- 第 51 至 53 行:当 [
@Parameter.required](http://svip.iocoder.cn/Dubbo/configuration-api-1/mailto:%60@Parameter.required)<font style="color:rgb(51, 51, 51);"> = true时,校验配置项非空。
- 第 14 至 17 行:返回值类型为 Object 或排除( [
- 第 54 至 57 行:当方法为 例如 #getParameters() 时, 。
- 第 58 行:通过反射,获得 #getParameters() 的返回值为 map 。
- 第 59 至 64 行:将 map 添加到 parameters ,kv 格式为 prefix:entry.key entry.value 。
- 因此,通过 动态设置配置项,拓展出非 Dubbo 内置好的逻辑 #getParameters() 对应的属性, 。
#appendAttributes(parameters, config, prefix) 方法,将 @Parameter(attribute = true) 配置对象的属性,添加到参数集合。代码如下:
1
protected static void appendAttributes(Map<Object, Object> parameters, Object config, String prefix) { if (config == null) { return; } Method[] methods = config.getClass().getMethods(); for (Method method : methods) { try { String name = method.getName(); if ((name.startsWith("get") || name.startsWith("is")) && !"getClass".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && isPrimitive(method.getReturnType())) { // 方法为获取基本类型,public 的 getting 方法。 Parameter parameter = method.getAnnotation(Parameter.class); if (parameter == null || !parameter.attribute()) continue; // 获得属性名 String key; if (parameter != null && parameter.key() != null && parameter.key().length() > 0) { key = parameter.key(); } else { int i = name.startsWith("get") ? 3 : 2; key = name.substring(i, i + 1).toLowerCase() + name.substring(i + 1); } // 获得属性值,存在则添加到 `parameters` 集合 Object value = method.invoke(config, new Object[0]); if (value != null) { if (prefix != null && prefix.length() > 0) { key = prefix + "." + key; } parameters.put(key, value); } } } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } }}
- 不同于 #appendAttributes(parameters, config, prefix)《Dubbo 用户指南 —— 事件通知》 方法,主要用于 ,注解 @Parameter(attribute = true) 的属性如下图:
@Parameter(attribute = true) - 第 9 至 13 行:方法为获得基本类型 + public 的 getting 方法。
- 第 14 至 16 行:需要[
@Parameter.exclue](http://svip.iocoder.cn/Dubbo/configuration-api-1/mailto:%60@Parameter.exclue)<font style="color:rgb(51, 51, 51);">=true)的配置项。 ( - 第 17 至 24 行:获得配置项名 。
- 第 26 至 30 行:获得配置项值 。
- 第 31 行:添加配置项到 parameters 。
#appendProperties(config) 方法,读取环境变量和 properties 配置到配置对象。在 《精进 Dubbo 源码解析 —— 属性配置》 详细解析。
#appendAnnotation(annotationClass, annotation) 方法,读取注解配置到配置对象。在 《精进 Dubbo 源码解析 —— 注解配置》 详细解析。
3.2 ApplicationConfig
com.alibaba.dubbo.config.ApplicationConfig ,应用配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:application》 文档。
3.3 RegistryConfig
com.alibaba.dubbo.config.RegistryConfig ,注册中心配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:registry》 文档。
3.4 ModuleConfig
com.alibaba.dubbo.config.ModuleConfig ,模块信息配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:module》 文档。
3.5 MonitorConfig
com.alibaba.dubbo.config.MonitorConfig ,监控中心配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:monitor》 文档。
3.6 ArgumentConfig
com.alibaba.dubbo.config.ArgumentConfig ,方法参数配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:argument》 文档。
- 该配置类设置到 MethodConfig 对象中,在 《API 配置(二)之服务提供者》 我们会看到。
- 在 《Dubbo 用户指南 —— 参数回调》 特性中使用。
4. URL
com.alibaba.dubbo.common.URL ,Dubbo URL 。代码如下:
1
2
3
public final class URL implements Serializable { /** * 协议名 */ private final String protocol; /** * 用户名 */ private final String username; /** * 密码 */ private final String password; /** * by default, host to registry
* 地址 */ private final String host; /** * by default, port to registry
* 端口 */ private final int port; /** * 路径(服务名) */ private final String path; /** * 参数集合 */ private final Map<String, String> parameters; // ... 省略其他代码}
- 上文我们提到所有配置最终都将转换为 Dubbo URL 表示,并由服务提供方生成,经注册中心传递给消费方,各属性对应 URL 的参数,参见配置项一览表中的 “对应URL参数” 列 。那么一个 Service 注册到注册中心的格式如下:
```plain text dubbo://192.168.3.17:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&default.delay=-1&default.retries=0&default.service.filter=demoFilter&delay=-1&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=19031&side=provider×tamp=1519651641799
1
2
3
```plain text
- <font style="color:rgb(51, 51, 51);">格式为 </font><font style="color:rgb(51, 51, 51);">protocol://username:password@host:port/path?key=value&key=value</font><font style="color:rgb(51, 51, 51);"> ,通过 </font>[URL#buildString(...)](https://github.com/YunaiV/dubbo/blob/9d38b6f9f95798755141d6140e311e8fd51fecc1/dubbo-common/src/main/java/com/alibaba/dubbo/common/URL.java#L1176-L1217)<font style="color:rgb(51, 51, 51);"> 方法生成。</font>
- parametersAbstractConfig#appendParameters(parameters, config, prefix) 属性,参数集合。从上面的 Service URL 例子我们可以看到,里面的 key=value ,实际上就是 Service 对应的配置项。该属性,通过 方法生成。
- 在后续的文章中,我们会发现 URL 作为一个通用模型 ,贯穿整个 RPC 流程。
5. @Parameter
com.alibaba.dubbo.config.support.@Parameter ,Parameter 参数注解,用于 Dubbo URL 的 parameters 拼接。
在配置对象的 getting 方法上,我们可以看到该注解的使用,例如下图:
@Parameter 使用场景
@Parameter 代码如下:
1
2
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface Parameter { /** * 键(别名) */ String key() default ""; /** * 是否必填 */ boolean required() default false; /** * 是否忽略 */ boolean excluded() default false; /** * 是否转义 */ boolean escaped() default false; /** * 是否为属性 * * 目前用于《事件通知》http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html
*/ boolean attribute() default false; /** * 是否拼接默认属性,参见 {@link com.alibaba.dubbo.config.AbstractConfig#appendParameters(Map, Object, String)} 方法。 * * 我们来看看 `#append() = true` 的属性,有如下四个: * + {@link AbstractInterfaceConfig#getFilter()} * + {@link AbstractInterfaceConfig#getListener()} * + {@link AbstractReferenceConfig#getFilter()} * + {@link AbstractReferenceConfig#getListener()} * + {@link AbstractServiceConfig#getFilter()} * + {@link AbstractServiceConfig#getListener()} * 那么,以 AbstractServiceConfig 举例子。 * * 我们知道 ProviderConfig 和 ServiceConfig 继承 AbstractServiceConfig 类,那么 `filter` , `listener` 对应的相同的键。 * 下面我们以 `filter` 举例子。 * * 在 ServiceConfig 中,默认会<b>继承</b> ProviderConfig 配置的 `filter` 和 `listener` 。 * 所以这个属性,就是用于,像 ServiceConfig 的这种情况,从 ProviderConfig 读取父属性。 * * 举个例子,如果 `ProviderConfig.filter=aaaFilter` ,`ServiceConfig.filter=bbbFilter` ,最终暴露到 Dubbo URL 时,参数为 `service.filter=aaaFilter,bbbFilter` 。 */ boolean append() default false;
- 胖友可以简单看下代码中的注释 ,结合具体使用的方法,在细细理解。



