第四章:旧SDK代码摸底——把老家伙们一个个揪出来
说实话,迁移项目刚启动那会儿,我最怕的不是改代码,而是连旧SDK用在哪都不知道。你想想看,一个跑了四五年的系统,几十个模块,上百个文件——靠肉眼去翻?不现实。
所以咱们的第一步,就是全面摸底。把旧SDK的足迹全部画出来。我习惯把这步叫做「代码考古」——不是贬义,是真的像考古一样,得一层层挖。
4.1 摸底的三条主线
旧SDK的引用一般分三类。我个人经验里,至少九成的问题都藏在这三个地方:
| 类型 | 特征 | 典型例子 |
|---|---|---|
| 注解 | 类、方法、参数上的标记 | @OldAuditLog, @Legacy("v1") |
| API调用 | 直接new旧SDK的类、调静态方法 | AuditSDK.submit(…), new AuditClient() |
| 配置 | XML、properties、YAML里的SDK参数 | audit.sdk.endpoint, sdk-config.xml |
我曾经接手过一个项目,团队说「我们没怎么用旧SDK」。结果我一扫配置,发现老SDK的Endpoint还在application.yml里躺着——只是没人记得删。嗯,这就是摸底的价值。
4.2 搜索策略:从粗到细
我不会一上来就写脚本。先做两件简单的事,能省很多时间。
- 全局文本搜索:用IDE或grep搜旧SDK的包名,比如
com.old.audit。这一步能覆盖九成以上的引用。 - 扫描导入语句:所有
import com.old.audit.*的地方,一个都跑不掉。
我习惯这么搜:
# Linux/Mac 命令行搜jar引用
grep -r "com\.old\.audit" src/ --include="*.java" --include="*.xml" --include="*.properties"
# 如果想统计出现次数
grep -rc "com\.old\.audit" src/ --include="*.java" | grep -v ":0$"
4.3 处理注解:别漏了反射场景
注解这块比较隐蔽。旧SDK可能在运行时用反射读取注解,这种代码在IDE里很难直接发现。我遇到过一回,一个自定义注解@LegacyRule根本没在任何地方显式import,但通过AOP切面拦截了所有带它的方法。
怎么查?
- 搜注解名称:比如
@OldAuditLog,搜所有.java和.kt文件。 - 搜注解的元数据:如果旧SDK定义了一个
@interface,搜@interface OldAuditLog就能找到定义位置。 - 检查AOP配置:Spring的
@Aspect或@Pointcut里,可能用@annotation(com.old.audit.OldAuditLog)这种方式引用。
@Inherited注解——父类上有旧SDK的注解,子类也会继承。我之前排查时漏了这一层,导致子类中的逻辑没被迁移。
4.4 API调用:最难缠的「硬编码」
直接调用的API比较好查,但有几类容易漏掉:
- 工厂模式:通过
AuditFactory.create()拿到的对象,实际返回的是旧SDK类。 - 静态导入:
import static com.old.audit.AuditUtils.submit;,然后代码里直接写submit(...),看着像本地方法。 - 反射调用:
Class.forName("com.old.audit.AuditClient"),这种最恶心,IDE搜不到字符串里的包名。
对反射调用的场景,我的建议是:运行时挂个切面或字节码工具,把所有Class.forName和Method.invoke的调用日志打出来。我当年用Arthas的watch命令抓过一次,十几分钟就定位了三个隐藏点。
4.5 配置扫描:容易被遗忘的角落
配置文件的格式五花八门。我的经验是列一个清单,然后逐个目录扫:
| 配置文件 | 典型路径 | 旧SDK参数示例 |
|---|---|---|
| YAML | src/main/resources/application.yml |
audit.old.endpoint: http://local:8080 |
| Properties | src/main/resources/*.properties |
audit.sdk.version=1.2.3 |
| XML | src/main/resources/spring/*.xml |
<bean class="com.old.audit.AuditService"/> |
| JSON | 配置文件、环境变量注入 | "audit_provider": "old_sdk" |
我个人还会检查Dockerfile和docker-compose.yml,有时候旧SDK的镜像环境变量写在里面,项目代码里反而没有。
4.6 自动化扫描脚本(推荐)
手动搜一遍后,最好写个脚本做二次确认。我给你个Python脚本的骨架,在项目中跑一遍,输出汇总报告:
import os
import re
OLD_SDK_PACKAGES = ["com.old.audit", "com.legacy.audit"]
SCAN_EXTENSIONS = [".java", ".xml", ".properties", ".yml", ".yaml", ".json"]
found = []
for root, dirs, files in os.walk("src"):
for file in files:
if any(file.endswith(ext) for ext in SCAN_EXTENSIONS):
path = os.path.join(root, file)
with open(path, "r", errors="ignore") as f:
for line in f:
for pkg in OLD_SDK_PACKAGES:
if pkg in line:
found.append(f"{path}: {line.strip()}")
break
print(f"共发现 {len(found)} 处引用")
for item in found:
print(item)
4.7 摸底流程全景图
下面这张图概括了上面讲的所有步骤。我建议你把它打印出来,贴在工位旁边,做摸底时照着走一遍。
4.8 避坑指南:来自实战的教训
lib/目录下残存着旧SDK的jar包,但代码里压根没import——结果运行时通过SPI自动加载了。所以摸底一定要包含lib/、WEB-INF/lib/、build/这些目录。
还有一次,我排查一个微服务项目,发现在gradle.properties和pom.xml里引用了旧SDK的版本号,虽然代码里没直接调用,但依赖传递带了一堆旧类进来。嗯,所以构建文件也要纳入摸底范围。
摸底不是一次性工作。我建议你做完一次全面扫描后,在CI流水线里加一个检测步骤——比如用grep或自定义插件,每次提交都检查是否新增了旧SDK的引用。这样后续迁移过程中,不会有人不小心又引入老代码。