看Dubbo源码总结出来的那些小技巧
小橘子🍊

前言

好久没有更新 Blog 了,一个重要的原因是抽不出时间呐,因为在看 Dubbo 的源码,到今天总算是把服务发布者和服务调用者的部分给看完了,而这篇文章就是在看 Dubbo 源码中学到的一些小技巧,不是很系统,但是很干货,如果自己看过源码就会更加的印象深刻。在看源码的过程中我还根据自己的理解添加了较为详细的注释,因为个人能力问题,不能保证全部正确,但也还是有一定的参考意义,如果你也是在看 Dubbo 源码的话可以参考一下我的代码注释,地址奉上。

正文

Stream 组合方法

org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator.hasInvocationArgument

1
2
3
4
5
private boolean hasInvocationArgument(Method method) {
Class<?>[] pts = method.getParameterTypes();
// 查看 method 是否有 org.apache.dubbo.rpc.Invocation 类型的参数
return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName()));
}

return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName())); 这条语句中,借用了 Arrays 工具类的 stream 方法得到 java.util.stream.Stream 实例,利用这个实例就可以进行类似于 scala 语法的一些函数式编程了。这里的 anyMatch 表示任一匹配的方式,如果找到了任意符合条件的 p,就返回 true,否则返回 false。

org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generateInvocationArgumentNullCheck

1
2
3
4
5
6
private String generateInvocationArgumentNullCheck(Method method) {
Class<?>[] pts = method.getParameterTypes();
return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
.mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
.findFirst().orElse("");
}

return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName())).mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i)).findFirst().orElse(""); 这条代码中,借用了 Arrays 工具类的 stream 方法得到 java.util.stream.Stream 实例,利用这个实例就可以进行类似于 scala 语法的一些函数式编程了。这里的 filter 表示过滤,即找到满足 CLASSNAME_INVOCATION.equals(pts[i].getName()) 的 i,mapToObj 函数表示映射,即将过滤出来的 i 通过映射转换为 String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i),findFirst 表示映射的终止条件,即找到任意 i 即返回,而 orElse 则是没有找到符合条件的 i 的处理方式,直接返回 “”。

org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generateReturnAndInvocation

1
2
3
4
5
6
7
8
private String generateReturnAndInvocation(Method method) {
// 如果方法的返回类型为 void,拼接 return
String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";

String args = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.joining(", "));

return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
}

String args = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.joining(", ")); 这条代码中,借用了 Arrays 工具类的 stream 方法得到 java.util.stream.Stream 实例,利用这个实例就可以进行类似于 scala 语法的一些函数式编程了。这里的 map 为映射函数,即将 stream 中的每一个元素调用 Parameter::getName 函数进行处理,这里的 Parameter::getName 代表函数引用,最后的 collect 表示将所有的处理结果进行搜集,collect 的参数表示搜集的方式。

一种获取 ClassLoader 的方式

org.apache.dubbo.common.utils.ClassUtils#getClassLoader(java.lang.Class<?>)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static ClassLoader getClassLoader(Class<?> clazz) {
ClassLoader cl = null;
try {
// 返回 Thread 的 contextClassLoader
cl = Thread.currentThread().getContextClassLoader();
} catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back to system class loader...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
// 返回 class 的类加载器,如果是 bootstrap 类加载器,将会返回 null
cl = clazz.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
} catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}

return cl;
}

这是一个获取类加载器的方式,有三个优先级,先是获取当前线程的 contextClassLoader,如果没有获取到,尝试获取加载该类 clazz 的类加载器,注意如果加载 clazz 的类加载器是 bootstrap 类加载器,那么返回值为 null,因为这个类加载器是通过 c++ 语言实现,通过 java 进行获取都是为 null。最后便是获取 SystemClassLoader,这里已经可以确定是获取的 bootstrap 类加载器了。

增强版的 isPrimitives 方法

org.apache.dubbo.common.utils.ReflectUtils#isPrimitives

1
2
3
4
5
6
7
8
9
10
11
public static boolean isPrimitives(Class<?> cls) {
if (cls.isArray()) {
return isPrimitive(cls.getComponentType());
}
return isPrimitive(cls);
}

public static boolean isPrimitive(Class<?> cls) {
return cls.isPrimitive() || cls == String.class || cls == Boolean.class || cls == Character.class
|| Number.class.isAssignableFrom(cls) || Date.class.isAssignableFrom(cls);
}

isPrimitives 一个增强版本的判断 class 是否是原始类型的函数,在 jdk 类库中,直接有一个函数 java.lang.Class#isPrimitive 就可以直接判断一个 class 实例是否是九种原始基本类型中的一种。在 isPrimitives 中增加了对 Array、String、Boolean、Charater、Number、Date 几种类型的判断支持。这里 Array 的判断是通过获取 java.lang.Class#getComponentType 来进行的。

Curator 框架的使用

org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient

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
public CuratorZookeeperClient(URL url) {
// 保存 url 地址
super(url);
try {
int timeout = url.getParameter(TIMEOUT_KEY, 5000);
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
// 备用地址
.connectString(url.getBackupAddress())
// 重试策略
.retryPolicy(new RetryNTimes(1, 1000))
// 连接超时策略
.connectionTimeoutMs(timeout);
// 获取授权信息
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
// 就是根据 builder 获取一个 CuratorFrameworkImpl
client = builder.build();
// 添加一个连接监听器
client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
// 如果连接状态发生改变,就会执行此方法
public void stateChanged(CuratorFramework client, ConnectionState state) {
if (state == ConnectionState.LOST) {
// 断开连接
CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
} else if (state == ConnectionState.CONNECTED) {
// 连接成功
CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
} else if (state == ConnectionState.RECONNECTED) {
// 重连
CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
}
}
});
client.start();
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}

这里我们重点关注 client = builder.build(); 这一条语句,返回值 client 其实就是 CuratorFrameworkImpl 实例,CuratorFramework 提供了一套高级的API,简化了 ZooKeeper 的操作,它增加了很多使用ZooKeeper开发的特性,可以处理 ZooKeeper 集群复杂的连接管理和重试机制。这些特性包括:

1、 自动化的连接管理: 重新建立到 ZooKeeper 的连接和重试机制存在一些潜在的错误 case。 Curator 帮助你处理这些事情,对你来说是透明的。

2、 简化了原生的ZooKeeper的方法,提供了一个现代的流式接口。

3、 提供了Recipes实现: 如前面的文章介绍的那样,基于这些Recipes可以创建很多复杂的分布式应用。

Curator 框架通过 CuratorFrameworkFactory 以工厂模式和 builder 模式创建 CuratorFramework 实例。 CuratorFramework 实例都是线程安全的,你应该在你的应用中共享同一个 CuratorFramework 实例。

工厂方法 newClient() 提供了一个简单方式创建实例。而 Builder 提供了更多的参数控制。一旦你创建了一个 CuratorFramework 实例,你必须调用它的 start() 启动,在应用退出时调用 close() 方法关闭。上边的代码便是 dubbo 对 实例,CuratorFramework 的一个使用示例。

一种可用端口的获取方式

org.apache.dubbo.common.utils.NetUtils#getAvailablePort(int)

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
public static int getAvailablePort() {
try (ServerSocket ss = new ServerSocket()) {
// 这种情况就会产生一个随机的端口
ss.bind(null);
return ss.getLocalPort();
} catch (IOException e) {
return getRandomPort();
}
}

public static int getAvailablePort(int port) {
if (port <= 0) {
// 这里是传入的 default 端口小于等于 0 的情况
return getAvailablePort();
}
for (int i = port; i < MAX_PORT; i++) {
// 从 port 端口逐个向上找可以使用的端口
try (ServerSocket ss = new ServerSocket(i)) {
return i;
} catch (IOException e) {
// continue
}
}
return port;
}

这里获取可用端口使用了两种策略,根据传入参数的不同情况分别采用不同策略,首先是传入端口小于等于 0 的情况,通过新建一个 ServerSocket 实例 ServerSocket ss = new ServerSocket(),再将它绑定到一个 null endPoint,这时通过 ss.getLocalPort() 便能获取到一个系统随机指定的端口了。如果传入的端口是大于 0 的,那么采用的策略就是逐次递增端口号,通过 ServerSocket ss = new ServerSocket(i) 来判定当前端口是否可用。

Future 和 Map 的组合操作

org.apache.dubbo.rpc.filter.AccessLogFilter#log

1
2
3
4
5
6
7
8
9
10
11
12
private void log(String accessLog, AccessLogData accessLogData) {
// 如果 LOG_ENTRIES 中没有键为 accessLog 的记录,就新建一个记录,以 accessLog 为键存入 LOG_ENTRIES 中
Set<AccessLogData> logSet = LOG_ENTRIES.computeIfAbsent(accessLog, k -> new ConcurrentHashSet<>());

if (logSet.size() < LOG_MAX_BUFFER) {
// accessLog 所对应的 AccessLogData 不应该超过 5000 条
logSet.add(accessLogData);
} else {
//TODO we needs use force writing to file so that buffer gets clear and new log can be written.
logger.warn("AccessLog buffer is full skipping buffer ");
}
}

Set<AccessLogData> logSet = LOG_ENTRIES.computeIfAbsent(accessLog, k -> new ConcurrentHashSet<>()); 这条代码的逻辑很简单,但是它利用了 java 的新特性,通过传入函数参数,简化了操作。computeIfAbsent 这个函数,先判断 map 中是否有 accessLog 这条记录,如果没有就新建一条记录,以 accessLog 为键进行存储,等价的就是一个 if-else 操作。

org.apache.dubbo.rpc.AsyncRpcResult#thenApplyWithContext

1
2
3
public void thenApplyWithContext(Function<Result, Result> fn) {
this.resultFuture = resultFuture.thenApply(fn.compose(beforeContext).andThen(afterContext));
}

这条代码看起来很简单对吧,但是实现的功能却比较复杂,传入的参数是一个 Function 实例,需要注意的是它的 compose 方法,这个方法意思是说构建出一个复合函数,以 fn.compose(beforeContext) 为例,那么它的意思是对于调用者来说,先使用 beforeContext 函数来处理传入的参数(即调用者本身),然后使用 fn 函数对 beforeContext 函数处理的结果进行处理。后边的 andThen 意思基本一致,只不过是先调用 andThen 方法的调用者对参数进行处理,而后再将处理的结果交由 afterContext 函数进行处理。结合上述的例子来看,那么基本的调用顺序就是:resultFuture -> beforeContext = result1,result1 -> fn = result2,result2 -> afterContext = result3,result3 就是最终要返回的结果。

Optional 的使用

org.apache.dubbo.config.AbstractInterfaceConfig#startConfigCenter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void startConfigCenter() {
// 这里对应 config-center 标签,存储在 ServiceBean 中
if (configCenter == null) {
// java.util.Optional.ifPresent 如果值存在,就调用函数,否则什么都不干
ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc);
}

// 初次调用时,如果没有配置 config-center 标签,则这里是为 null 的
if (this.configCenter != null) {
// TODO there may have duplicate refresh
this.configCenter.refresh();
// 准备运行环境
prepareEnvironment();
}
// 单例获取 ConfigManager,依据具体的配置信息对相应的 config 进行属性的更新
ConfigManager.getInstance().refreshAll();
}

public Optional<ConfigCenterConfig> getConfigCenter() {
return Optional.ofNullable(configCenter);
}

这里关注 ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc); 这条代码,getConfigCenter 方法返回的是一个 Optional 实例,它有一个 ifPresent 方法,参数是一个 @FunctionalInterface 注解标注的函数式接口,这样我们在 ifPresent 只需要传入一个函数进去,返回的 Optional 就能根据它自身的状态来决定是否要应用这个函数,这个过程其实就是简化了我们自己手写 if-else 判断代码的过程。

Version 的获取方式

org.apache.dubbo.common.Version#getVersion(java.lang.Class<?>, java.lang.String)

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
public static String getVersion(Class<?> cls, String defaultVersion) {
try {
// find version info from MANIFEST.MF first
// 根据 cls 查找 version 信息,存储在 MANIFEST.MF 文件中
Package pkg = cls.getPackage();
String version = null;
if (pkg != null) {
version = pkg.getImplementationVersion(); // 此为获取 MANIFEST.MF 文件中版本信息的函数,比如 Implementation-Version:
if (!StringUtils.isEmpty(version)) {
return version;
}

version = pkg.getSpecificationVersion(); // 此为获取 MANIFEST.MF 文件中版本信息的函数,比如 Specification-Version
if (!StringUtils.isEmpty(version)) {
return version;
}
}

// guess version fro jar file name if nothing's found from MANIFEST.MF
// 没能从 MANIFEST.MF 文件中获取到 version 信息
CodeSource codeSource = cls.getProtectionDomain().getCodeSource();
// 估计是由于某种原因,没能获取到 codeSource,那就使用参数传递的 defaultVersion 信息
if (codeSource == null) {
logger.info("No codeSource for class " + cls.getName() + " when getVersion, use default version " + defaultVersion);
return defaultVersion;
}
// 那就根据 jar 包推测版本信息
String file = codeSource.getLocation().getFile();
if (!StringUtils.isEmpty(file) && file.endsWith(".jar")) {
version = getFromFile(file);
}

// return default version if no version info is found
return StringUtils.isEmpty(version) ? 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;
}
}

这是 dubbo 公共包中用于获取版本信息的函数,该函数优先从 cls 所在 package 中的 MANIFEST.MF 文件中获取版本信息,有 Implementation-Version 和 Specification-Version 两种情况,分别对应 getImplementationVersion 和 getSpecificationVersion 函数,如果在 MANIFEST.MF 文件中没有能获取到版本信息,那么该函数就会根据该 cls 所在的 jar 包来获取相应的版本信息,如果还是没有获取到的话,那么就会直接使用函数的默认版本参数值。

动态代码和字节码的生成

org.apache.dubbo.common.bytecode.Wrapper#makeWrapper

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
private static Wrapper makeWrapper(Class<?> c) {
// 不能对 9 中基本类型的包装类型构建 Wrapper
if (c.isPrimitive()) {
throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
}

String name = c.getName();
ClassLoader cl = ClassUtils.getClassLoader(c);

// 主要是对三个函数进行拼接
// setPropertyValue
// getPropertyValue
// invokeMethod
// public void setPropertyValue (Object o, String n, Object v){
StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
// public Object getPropertyValue(Object o, String n){
StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
// public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException{
StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");

// 根据 name 构造出这样的代码
// public void setPropertyValue (Object o, String n, Object v){
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
// public Object getPropertyValue (Object o, String n){
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
// public Object invokeMethod (Object o, String n, Class[]p, Object[]v) throws
// java.lang.reflect.InvocationTargetException {
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");

// 保存字段的 name 和 type
Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types>
// 保存方法的 desc(签名) 和 instance
// get method desc.
// int do(int arg1) => "do(I)I"
// void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V"
Map<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance>
// 保存方法名
List<String> mns = new ArrayList<>(); // method names.
// 保存声明方法的类名
List<String> dmns = new ArrayList<>(); // declaring method names.

// get all public field.
for (Field f : c.getFields()) {
String fn = f.getName();
Class<?> ft = f.getType();
// 避开 static 和 transient 修饰的字段
if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) {
continue;
}

// 根据 field 进行构建
// public void setPropertyValue (Object o, String n, Object v){
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// if( $2.equals("serviceName")){
// w.serviceName=(org.apache.dubbo.demo.DemoService) $3;
// return;
// }
c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
// public Object getPropertyValue (Object o, String n){
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// if( $2.equals("serviceName")){
// return ($w)w.serviceName;
// }
c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");
// 保存字段的 name 和 type
pts.put(fn, ft);
}

Method[] methods = c.getMethods();
// get all public method.
// methods 方法不为空,且不全为 Object 声明的方法
boolean hasMethod = hasMethods(methods);
if (hasMethod) {
// 通过 methods 对 c3 进行构造
c3.append(" try{");
for (Method m : methods) {
//ignore Object's method.
if (m.getDeclaringClass() == Object.class) {
continue;
}

// 通过方法名进行构造
String mn = m.getName();
c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
int len = m.getParameterTypes().length;
// 补上方法参数信息
// public Object invokeMethod (Object o, String n, Class[]p, Object[]v) throws java.lang.reflect.InvocationTargetException {
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// try {
// if ("sayHello".equals($2) && $3.length == 1
c3.append(" && ").append(" $3.length == ").append(len);

boolean override = false;
for (Method m2 : methods) {
// 如果有跟当前方法同名的其它方法,即存在重写(overwrite)
if (m != m2 && m.getName().equals(m2.getName())) {
override = true;
break;
}
}
if (override) {
if (len > 0) {
for (int l = 0; l < len; l++) {
// 补上方法参数信息
// public Object invokeMethod (Object o, String n, Class[]p, Object[]v) throws java.lang.reflect.InvocationTargetException {
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// try {
// if ("sayHello".equals($2) && $3.length == 1 && $3[l].getName().equals("java.lang.String")){;

c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
.append(m.getParameterTypes()[l].getName()).append("\")");
}
}
}
// public Object invokeMethod (Object o, String n, Class[]p, Object[]v) throws java.lang.reflect.InvocationTargetException {
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// try {
// if ("sayHello".equals($2) && $3.length == 1) {
c3.append(" ) { ");

// 拼接方法的返回值
if (m.getReturnType() == Void.TYPE) {
c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
} else {
c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
}

// 拼接完返回值后
// public Object invokeMethod (Object o, String n, Class[]p, Object[]v) throws java.lang.reflect.InvocationTargetException {
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// try {
// if ("sayHello".equals($2) && $3.length == 1) {
// return ($w) w.sayHello((java.lang.String) $4[0]);
// }
c3.append(" }");

// 保存拼接的方法名
mns.add(mn);
if (m.getDeclaringClass() == c) {
// 保存声明方法的类名
dmns.add(mn);
}
// 保存方法的签名和方法实例
// get method desc.
// int do(int arg1) => "do(I)I"
// void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V"
ms.put(ReflectUtils.getDesc(m), m);
}
c3.append(" } catch(Throwable e) { ");
c3.append(" throw new java.lang.reflect.InvocationTargetException(e); ");
c3.append(" }");
}

// 补全括号即剩余信息
// o 方法调用的目标对象,n 方法名,p 方法的参数类型,v 方法的参数
// public Object invokeMethod (Object o, String n, Class[]p, Object[]v) throws java.lang.reflect.InvocationTargetException {
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// try {
// if ("sayHello".equals($2) && $3.length == 1) {
// return ($w) w.sayHello((java.lang.String) $4[0]);
// }
// } catch (Throwable e) {
// throw new java.lang.reflect.InvocationTargetException(e);
// }
// throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class org.apache.dubbo.demo.DemoService.");
// }
c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");

// deal with get/set method.
Matcher matcher;
// 这里 ms 保存的是接口中非 Object 类方法的签名和方法实例
for (Map.Entry<String, Method> entry : ms.entrySet()) {
// 方法签名
String md = entry.getKey();
// 方法实例
Method method = entry.getValue();
// 匹配 getter 方法
if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
String pn = propertyName(matcher.group(1));
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType());
} else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
// 匹配 is、has、can 方法
String pn = propertyName(matcher.group(1));
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType());
} else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
// 匹配 setter 方法
Class<?> pt = method.getParameterTypes()[0];
String pn = propertyName(matcher.group(1));
c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
pts.put(pn, pt);
}
}
// c1 的拼接结果
// public void setPropertyValue (Object o, String n, Object v){
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// throw new org.apache.dubbo.common.bytecode.NoSuchPropertyException("Not found property \"" + $2 + "\" field or setter method in class org.apache.dubbo.demo.DemoService.");
// }
c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");

// c2 的拼接结果
// public Object getPropertyValue (Object o, String n){
// org.apache.dubbo.demo.DemoService w;
// try {
// w = ((org.apache.dubbo.demo.DemoService) $1);
// } catch (Throwable e) {
// throw new IllegalArgumentException(e);
// }
// throw new org.apache.dubbo.common.bytecode.NoSuchPropertyException("Not found property \"" + $2 + "\" field or setter method in class org.apache.dubbo.demo.DemoService.");
// }
c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");

// make class
// 对构建的 wrapper 类进行计数
long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
// 根据 classLoader 构建 ClassGenerator
ClassGenerator cc = ClassGenerator.newInstance(cl);
// 补全一些类信息
cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
cc.setSuperClass(Wrapper.class);

cc.addDefaultConstructor();
cc.addField("public static String[] pns;"); // property name array.
cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.
cc.addField("public static String[] mns;"); // all method name array.
cc.addField("public static String[] dmns;"); // declared method name array.
for (int i = 0, len = ms.size(); i < len; i++) { // 方法的参数类型数组,有几个函数就会有几个数组
cc.addField("public static Class[] mts" + i + ";");
}

cc.addMethod("public String[] getPropertyNames(){ return pns; }");
cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
cc.addMethod("public String[] getMethodNames(){ return mns; }");
cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
cc.addMethod(c1.toString());
cc.addMethod(c2.toString());
cc.addMethod(c3.toString());

try {
// 根据 ClassGenerator 生成真正的 Class 对象
Class<?> wc = cc.toClass();
// setup static field.
// 根据上边获取的信息对 Class 对象的某些属性进行填充
// 字段的 name 和 type
wc.getField("pts").set(null, pts);
// pns 集合专门用来保存字段的名字
wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
// mns 集合专门用来保存方法名
wc.getField("mns").set(null, mns.toArray(new String[0]));
// dmns 集合专门用来保存方法的声明类
wc.getField("dmns").set(null, dmns.toArray(new String[0]));
int ix = 0;
// ms 保存的是方法签名和方法实例
// 遍历方法实例
for (Method m : ms.values()) {
// mts 字段用来保存方法的参数类型
wc.getField("mts" + ix++).set(null, m.getParameterTypes());
}
// 返回构建的 Wrapper 实例
return (Wrapper) wc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
// 对相关资源进行释放
cc.release();
ms.clear();
mns.clear();
dmns.clear();
}
}

这是 dubbo 用于根据服务接口生成对应 Wrapper 实例的代码,是一个动态代码生成的例子,过程非常的冗长,但是它所要实现的目的很简单,主要是三个方法 setPropertyValuegetPropertyValueinvokeMethod,当然还有几个和服务接口类相关的字段,比如说 public static String[] pns; 用于存储字段的类型名、public static java.util.Map pts; 用于保存字段的名字和类型对、public static String[] mns; 用于保存方法名、public static String[] dmns; 用于保存方法声明的类名、public static Class[] mts0; 用于保存方法的参数类型,这个数字 0 代表方法的序列。再有就是用于获取这些字段的 getter 方法,内容比较多,在下边的代码框中统一贴出。以上这些都是生成的 Wrapper 实例的代码,属于动态生成的代码,除此之外,代码还根据相应的接口信息,对相关的字段内容进行了统计和填充比如 wc.getField("pts").set(null, pts); wc.getField("pns").set(null, pts.keySet().toArray(new String[0])); 等代码都是将统计的内容添加到将要生成的 Wrapper 类的字段中去。至于是如何通过动态代码生成相应的 Class 类,就要靠 org.apache.dubbo.common.bytecode.ClassGenerator 这个类了,底层的原理暂时没有了解,大致猜测是使用的 Javassit 相关的字节码生成技术,这是属于 java 中比较靠底层的技术了,暂时是没有精力去深入了解了,暂且挖个坑吧。在通过 ClassGenerator 生成了对应的 Class 对象后,就是构建 Wrapper 实例,这个就跟使用普通的 Class 对象一样了,所以这里的技术难点就是在于如何生成动态代码,和如何根据动态代码来生成相应的 Class 对象了。

一种特殊的静态字段的初始化方式

org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#getInjvmProtocol

1
2
3
4
5
6
7
8
9
10
11
12
private static InjvmProtocol INSTANCE;

public InjvmProtocol() {
INSTANCE = this;
}

public static InjvmProtocol getInjvmProtocol() {
if (INSTANCE == null) { // 加载的过程会调用对应 class 的 newInstance 方法,就会对 INSTANCE 进行初始化
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(InjvmProtocol.NAME); // load
}
return INSTANCE;
}

这是 dubbo 获取 InjvmProtocol 的方法,获取的方式是先从 INSTANCE 缓存中获取,如果为空的话,就会执行 ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(InjvmProtocol.NAME); 这条语句,然后再返回 INSTANCE,需要注意的是这里好像并么有对 INSTANCE 进行赋值,如果 INSTANCE 为空,那么执行上述代码后 INSTANCE 也一定为空不是吗?但是通过代码调试,可以发现执行完 if 中的代码后 INSTANCE 就已经是被赋值为 InjvmProtocol 了,而就 INSTANCE 本身来说,它只有在 InjvmProtocol 类的构造函数中会有一个写操作,那就是说一定是在上述代码语句中执行了 InjvmProtocol 的构造函数,通过代码跟踪可以发现在如下代码中调用了它的构造方法。

org.apache.dubbo.common.extension.ExtensionLoader#createExtension

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
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// 先尝试从 cachedClasses 获取别名为 name 的 class
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 尝试从 EXTENSION_INSTANCES 获取 Extension 实例
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 没有获取到,那就直接通过 class 创建即可
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 根据 setter 方法,为 instance 实例填充 extension 属性
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
// 根据 setter 方法,为 wrapper 实例填充 extension 属性
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}

注意这里的 Class<?> clazz = getExtensionClasses().get(name); 语句获取的就是 InjvmProtocol 对应的 class 了,然后执行了 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); 语句来完成对 INSTANCE 的初始化。还有一点需要注意的是,InjvmProtocol 和它的构造函数都是属于 public 关键字修饰,也就是说任何代码都可以调用 InjvmProtocol 的构造函数,这样就会使得 INSTANCE 并非单例了,因为每一次 new InjvmProtocol 都会使得 INSTANCE 引用新的实例。

资源加载的方式

org.apache.dubbo.common.extension.ExtensionLoader#loadDirectory

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
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
// META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
String fileName = dir + type; // META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfigurationFactory
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
// 通过 classloader 获取 filename 资源的全部 urls
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
// /Users/eva/IdeaProjects/dubbo-with-comment/dubbo-common/target/classes/META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
// /Users/eva/IdeaProjects/dubbo-with-comment/dubbo-config/dubbo-config-spring/target/classes/META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
java.net.URL resourceURL = urls.nextElement();
// 将 resourceURL 所对应的文件中的 class 进行加载,然后保存到 extensionClasses
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}

这是 Dubbo 在进行 ExtensionClass 获取的代码,核心的就是 urls = classLoader.getResources(fileName); 这条代码,它会将资源路径下所有的 fileName 资源作为 Enumeration<java.net.URL> 返回,然后我们就可以针对每一个资源文件进行处理了。

总结

本文的篇幅算是比较长了,总结就尽量短吧,因为内容都是一些零散的技巧性总结,所以看起来不会觉得很系统,其中的一些要点可能还是要通过自己在阅读源码中领会了,看源码确实很枯燥乏味,但是如果不去看一些好的东西,只管自己折腾的话,那么可以说自己写出来的东西就是一坨 Shit。