API 配置(三)之服务消费者
本文基于 Dubbo 2.6.1 版本,望知悉。
友情提示,【配置】这块的内容,会相对比较枯燥。所以,如果看到一些很难懂的地方,建议先跳过。
对于 Dubbo ,重点是要去理解,多协议、RPC、容错等等模块,而不是【配置】。
估计好多胖友被【配置】这章劝退了把???
1. 概述
本文接 《API 配置(二)之服务提供者》 ,分享服务消费者相关的配置。
配置类关系
- 红框 部分,consumer-side
还是老样子,我们先来看一段 《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(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
2. AbstractReferenceConfig
com.alibaba.dubbo.config.AbstractReferenceConfig ,实现 AbstractInterfaceConfig ,抽象引用配置类。
- 具体属性的解释,需要寻找《Dubbo 用户指南 —— dubbo:reference》《Dubbo 用户指南 —— dubbo:consumer》 在 或 文档。
3. ConsumerConfig
com.alibaba.dubbo.config.ConsumerConfig ,实现 AbstractReferenceConfig ,服务消费者缺省值配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:consumer》 文档。
4. ReferenceConfig
com.alibaba.dubbo.config.ReferenceConfig ,服务消费者引用服务配置类。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:consumer》 文档。
下面,我们进入正戏。
在文初的 ReferenceConfig 的初始化示例代码中,最后调用的是 ServiceConfig#get() 方法。从方法的命名,我们可以看出,获取引用服务。该方法主要做了如下几件事情:
- 进一步初始化 ReferenceConfig 对象。
- 校验 ReferenceConfig 对象的配置项。
- 使用 ReferenceConfig 对象,生成数组 Dubbo URL 对象 。
- 使用 Dubbo URL 对象,应用服务 。
本文重点在服务提供者相关的配置,因此只解析 1+2+3 部分( 不包括 4 )。代码如下:
```plain text public synchronized T get() { // 已销毁,不可获得 if (destroyed) { throw new IllegalStateException(“Already destroyed!”); } // 初始化 if (ref == null) { init(); } return ref; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
- 第 2 至 5 行:若已经销毁(
destroyed = true
),抛出异常。
- 第 7 至 9 行:若未初始化,调用
#init()
方法,进行初始化。
- 第 10 行:返回引用服务。
---
[#init()](http://svip.iocoder.cn/Dubbo/configuration-api-3/) 方法,代码如下:
友情提示,该方法并未拆分更多的小方法,所以超级长,近 200+ 行。
```plain text
private void init() {
// 已经初始化,直接返回
if (initialized) {
return;
}
initialized = true;
// 校验接口名非空
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> 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);
}
// 直连提供者,参见文档《直连提供者》http://dubbo.apache.org/zh-cn/docs/user/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<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
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<String>(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.apache.org/zh-cn/docs/user/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);
// 省略【引用服务】
}
- 第 2 至 6 行:若已经初始化( initialized = true ) 时,直接返回。否则,标记已经初始化。
- 第 7 至 10 行:校验接口名 interfaceName 非空。
- 第 13 行:调用 #checkDefault()属性配置 方法,读取 ( 环境变量 + properties 属性 )到 ConsumerConfig 对象。
- 关于“属性配置《精尽 Dubbo 源码解析 —— 属性配置》 ” ,在 详细解析。
- 直接点击方法查看,较为简单,已经添加详细注释。
- 第 15 行:调用 #appendProperties(config)属性配置自己 方法,读取 ( 环境变量 + properties 属性 )到 ReferenceConfig 对象( )
- 第 16 至 19 行:若未设置 genericConsumerConfig.generic 属性,使用 属性。
- 第 20 至 22 行:泛化接口的实现。
- 第 23 至 32 行:普通接口的实现。
- 第 60 至 64 行:根据 interfaceName接口类interfaceClass ,获得对应的 ,并赋值给 。
- 第 31 行:调用 #checkInterfaceAndMethods(interfaceClass, methods) 方法,检查接口和方法。
- 直接点击方法查看,较为简单,已经添加详细注释。
- 第 33 至 76 行:直连提供者。
- 《Dubbo 用户指南 —— 直连提供者》
- 中间有一些逻辑处理,胖友看下代码的注释。结合文档。
- 第 77 至 109 行:从 ConsumerConfig、ModuleConfig、ApplicationConfig 配置对象,复制 自己 application module registries monitor 给 ReferenceConfig ( )。
- 第 111 行:调用 #checkApplication() 方法,校验 ApplicationConfig 配置。
- 直接点击方法查看,较为简单,已经添加详细注释。
- 第 113 行:调用 #checkStubAndMock(interfaceClass) 方法,校验 Stub 和 Mock 相关的配置。
- 第 115 行:创建参数集合 map ,用于下面创建 Dubbo URL 的 **parameters 属性** 。
- 第 116 至 122 行:将 side dubbo timestamp timestamp pid 添加到 map 中。
- 第 123 至 137 行:将 interface methods revision 到 map 中。
- 第 139 至 143 行:调用 #appendParameters(map, config) 方法,将各种配置对象添加到 map 中。
- #appendParameters(map, config)《API 配置(一)之应用》 方法,在 有详细解析。
- 第 146 至 164 行:调用 MethodConfig 对象数组 ,添加到 map 中。
- 目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到 map 中。
- 第 160 行:调用 #appendAttributes(parameters, config, prefix)《API 配置(一)之应用》 方法,将 @Parameter(attribute = true) 配置对象的属性,添加到参数集合。在 有详细解析。
- 第 162 行:调用 #checkAndConvertImplicitConfig(method, map, attributes) 方法,检查属性集合中的事件通知方法是否正确。若正确,进行转换。
- 直接点击方法查看,较为简单,已经添加详细注释。
- 第 166 至 173 行:以系统换将变量 ( DUBBO_IP_TO_REGISTRY ) 作为服务注册地址,参见 dubbo-docker-sample 项目。
- 第 177 行:添加到 com.alibaba.dubbo.rpc.StaticContext 进行缓存。
- 目的是 《Dubbo 用户指南 —— 事件通知》 。
- 第 179 行:省略 【服务引用】逻辑。
一本正经的水更和 《API 配置(二)之服务提供者》 大体类似。
