#!/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()