p2_strip_dubbo.py
9.84 KB
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
P2 第二步:把存量代码里的 Dubbo 依赖摘干净。
## 为什么可以机械替换
全仓 656 个文件 import 了 `com.alibaba.dubbo.*`,但**没有一个**是 RPC 用法
(`@Reference` / `@Service(com.alibaba.dubbo...)` 均为 0)——跨模块调用全部写在
31 个 `dubbo/*-consumer.xml` 里,靠 XML 装配。Java 侧用到的只有 Dubbo 自带的三个
工具类,实测调用点如下:
com.alibaba.dubbo.common.utils.StringUtils 412 文件
isNotEmpty 450 / isBlank 423 / isEmpty 311 / substringAfterLast 8 / isNotBlank 6 / isEquals 1
com.alibaba.dubbo.common.utils.CollectionUtils 401 文件
isNotEmpty 938 / isEmpty 684(老仓库实测口径;只有这两个方法)
com.alibaba.dubbo.common.json.JSON 12 文件
parse(String, Class) 17
com.alibaba.dubbo.common.json.ParseException 8 文件(JSON.parse 的受检异常)
com.alibaba.dubbo.rpc.RpcException 4 文件(catch 分支,Dubbo 专用)
com.alibaba.dubbo.rpc.Result 1 文件(未使用的 import)
替换目标选的都是**语义等价**的标准库,不是随便挑的:
- StringUtils → org.apache.commons.lang3.StringUtils
isEmpty/isNotEmpty/isBlank/isNotBlank/substringAfterLast 方法名与语义完全一致;
dubbo 的 isEquals 在 commons-lang3 里叫 equals(仅 1 处,单独改)。
- CollectionUtils → org.apache.commons.collections4.CollectionUtils
⚠️ **不能选 Spring 那份**(第一版就是错在这里,见下)。
Dubbo 的 CollectionUtils 是从 commons-collections 抄的,`isEmpty` 与
`isNotEmpty` 都有;而 `org.springframework.util.CollectionUtils`
**只有 `isEmpty`,没有 `isNotEmpty`**——从 3.1 到 7.0 的每一个版本都没有
(已用 javap 逐个核对 ~/.m2 里的 38 个 spring-core)。存量代码里
`isNotEmpty` 有 938 处调用,换到 Spring 那份必然编译不过。
选 commons-collections4 是因为它与 Dubbo 那份**同源**,
实测调用点只有 `isEmpty` / `isNotEmpty` 两个方法,语义完全一致。
- JSON.parse(s, Class) → com.alibaba.fastjson.JSON.parseObject(s, Class)
dubbo 的 JSON 本来就是 fastjson 的一层包装,parse 内部就是 parseObject。
- ParseException → com.alibaba.fastjson.JSONException
⚠️ 关键:dubbo 的 ParseException 是**受检异常**,fastjson 的是 RuntimeException。
直接把 import 换掉而不动 catch 会编译不过(checked 异常未被抛出)。
换成 JSONException 后进入 catch 的条件与原来完全一致(都是"解析失败"),
语义不变,只是不再需要 try 里显式声明。
- RpcException → 整个 catch 分支删除
Dubbo 没了,这个类型不可能再出现;本来的 `catch(Exception)` 分支会接住同样的异常。
⚠️ 3 个 MQ 监听器里原分支是 `throw e` 让 MQ 重投,删掉后异常改由后面的
`catch(Exception)` 吞掉(不重投)。这是丢 Dubbo 的必然结果,已记录在
docs/P2-并入清单核对.md,需回归确认重投语义是否有业务依赖。
## 幂等
全部是"找不到就不改",可重复执行。
"""
import os
import re
import sys
ROOT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DIRS = [
"parking-module-api",
"parking-module-park",
"parking-module-order",
"parking-module-pay",
"parking-module-bill",
"parking-module-charge",
"parking-module-business",
"parking-module-system",
"parking-framework/parking-legacy-common",
]
stats = {}
def bump(k, n=1):
stats[k] = stats.get(k, 0) + n
def java_files():
for d in DIRS:
base = os.path.join(ROOT, d)
for root, _, files in os.walk(base):
for f in files:
if f.endswith(".java"):
yield os.path.join(root, f)
# ---------------------------------------------------------------------------
# 规则:(说明, 正则, 替换, 计数键)
# ---------------------------------------------------------------------------
RULES = [
# E1 工具类
(
"StringUtils → commons-lang3",
re.compile(r"^import com\.alibaba\.dubbo\.common\.utils\.StringUtils;\s*$", re.M),
"import org.apache.commons.lang3.StringUtils;",
"stringutils",
),
(
"CollectionUtils → commons-collections4",
re.compile(r"^import com\.alibaba\.dubbo\.common\.utils\.CollectionUtils;\s*$", re.M),
"import org.apache.commons.collections4.CollectionUtils;",
"collectionutils",
),
# E2 JSON
(
"dubbo JSON → fastjson JSON",
re.compile(r"^import com\.alibaba\.dubbo\.common\.json\.JSON;\s*$", re.M),
"import com.alibaba.fastjson.JSON;",
"json-import",
),
(
"全限定 dubbo JSON.parse → fastjson",
re.compile(r"com\.alibaba\.dubbo\.common\.json\.JSON\.parse\("),
"com.alibaba.fastjson.JSON.parseObject(",
"json-fqn-parse",
),
# E3 ParseException
(
"dubbo ParseException → fastjson JSONException",
re.compile(r"^import com\.alibaba\.dubbo\.common\.json\.ParseException;\s*$", re.M),
"import com.alibaba.fastjson.JSONException;",
"parseexc-import",
),
# E5 无用 import
(
"删除 dubbo rpc.Result import",
re.compile(r"^import com\.alibaba\.dubbo\.rpc\.Result;\s*$\n?", re.M),
"",
"result-import",
),
]
# 需要额外改名/删分支的定点改动
TEXT_RULES = [
# ParseException 类型名 → JSONException(配合上面的 import 替换)
# ⚠️ 不能全局替换 `ParseException`:有些文件同时 import 了 java.text.ParseException
# 并在别处用。只处理"导入的是 dubbo 那份"的文件,见 apply_parse_exception()。
]
# ⚠️ 这几个正则**不能吞掉前面的 `}`**——那个 `}` 是 try / if 的收尾,
# 吞掉就变成语法错误。只删 `catch(...) { ... }` 本身,后面紧跟的 `catch`/`else` 自然接上。
#
# try { ... }catch (RpcException e) { log(); throw e; }catch(Exception e){
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 只删这一段
# → try { ... }catch(Exception e){ ✅ 结构不变
#
# 分支体实测都不含花括号(只有 log/赋值/throw),所以用 [^{}]* 足够。
RPC_CATCH = re.compile(r"[ \t]*catch\s*\(\s*RpcException\s+\w+\s*\)\s*\{(?:[^{}])*\}[ \t]*", re.M)
RPC_INSTANCEOF = re.compile(
r"[ \t]*else\s+if\s*\(\s*\w+\s+instanceof\s+RpcException\s*\)\s*\{(?:[^{}])*\}[ \t]*",
re.M,
)
def apply(path):
src = open(path, encoding="utf-8", errors="surrogateescape").read()
orig = src
touched = []
for desc, pat, repl, key in RULES:
new, n = pat.subn(repl, src)
if n:
# 去重:替换后若与已有 import 重复,则删掉新加的那行
src = dedupe_import(new, repl, key)
bump(key, n)
touched.append(f"{desc}x{n}")
# --- E1 特例:StringUtils.isEquals → equals ---
if "com.alibaba.dubbo.common.utils.StringUtils" in orig or "org.apache.commons.lang3.StringUtils" in src:
new, n = re.subn(r"\bStringUtils\.isEquals\(", "StringUtils.equals(", src)
if n:
src = new
bump("stringutils-isequals", n)
touched.append(f"StringUtils.isEquals→equals x{n}")
# --- E3 特例:把 dubbo ParseException 的类型名改成 JSONException ---
# ⚠️ 必须确认该文件的 ParseException 就是 dubbo 那一份:
# 极少数文件同时 import 了 java.text.ParseException 并在别处使用,改名会误伤。
if "import com.alibaba.dubbo.common.json.ParseException;" in orig:
if "import java.text.ParseException;" in orig:
raise SystemExit(f"!! {path}: 同时导入了 dubbo 与 java.text 两个 ParseException,需人工处理")
src, n = re.subn(r"\bParseException\b", "JSONException", src)
if n:
bump("parseexc-rename", n)
touched.append(f"ParseException→JSONException x{n}")
# --- E2 特例:dubbo JSON 只有 parse(s, Class),fastjson 对应 parseObject ---
if "import com.alibaba.dubbo.common.json.JSON;" in orig:
src, n = re.subn(r"\bJSON\.parse\(", "JSON.parseObject(", src)
if n:
bump("json-parse→parseobject", n)
touched.append(f"JSON.parse→JSON.parseObject x{n}")
# --- E4 RpcException catch 分支 ---
if "RpcException" in src:
src, n1 = RPC_CATCH.subn("", src)
src, n2 = RPC_INSTANCEOF.subn("", src)
src, n3 = re.subn(r"^import com\.alibaba\.dubbo\.rpc\.RpcException;\s*$\n?", "", src, flags=re.M)
if n1 or n2 or n3:
bump("rpcexception", n1 + n2 + n3)
touched.append(f"RpcException 分支删除 x{n1+n2+n3}")
if src != orig:
open(path, "w", encoding="utf-8", errors="surrogateescape").write(src)
return touched
return None
def dedupe_import(src, new_import, key):
"""如果 new_import 在文件里出现 >=2 次,说明原本就有同款 import,保留一处。"""
lines = src.split("\n")
seen = 0
out = []
for ln in lines:
if ln.strip() == new_import.strip():
seen += 1
if seen > 1:
continue # 丢掉后出现的那一条(= 我们刚插入的)
out.append(ln)
return "\n".join(out)
def main():
changed = 0
for p in java_files():
r = apply(p)
if r:
changed += 1
print(f" {os.path.relpath(p, ROOT)}")
for t in r:
print(f" · {t}")
print(f"\n改动文件 {changed} 个")
print("规则命中统计:")
for k, v in sorted(stats.items(), key=lambda x: -x[1]):
print(f" {k:<24} {v}")
if __name__ == "__main__":
main()