类注册机制
基本信息
| 属性 | 值 |
|---|---|
| 入口类 | minetweaker/mc1710/MineTweakerRegistry.java |
| 生成任务 | minetweaker.tasks.RegisterZenClassesTask(buildSrc/src/main/java/minetweaker/tasks/RegisterZenClassesTask.java) |
| 挂载点 | addon.gradle:3-9(makeRegistry 依赖 compileJava,classes 依赖 makeRegistry) |
| 扫描目标 | build/classes/java/main/(编译产物,非源码目录) |
| 扫描方式 | ASM ClassReader + SKIP_CODE,只看类级与方法级注解 |
| 识别注解 | @ZenClass、@ZenExpansion、@BracketHandler(RegisterZenClassesTask.java:151-161) |
| 额外钩子 | @OnRegister 静态无参 void 方法(RegisterZenClassesTask.java:182-190) |
这是理解「哪些类真正进入脚本命名空间」的关键:源码里的 MineTweakerRegistry 是个空壳桩,方法体在构建期被字节码重新生成并覆盖同名 .class。
桩文件
mc1710/MineTweakerRegistry.java:14-19 全文:
public class MineTweakerRegistry {
public static void getClasses(List<Class> classes) {
// method stub, actual content is generated by build process
}
}
生成逻辑
RegisterZenClassesTask.doTask()(RegisterZenClassesTask.java:52-103)用 ASM 现场合成一个新类:
iterate(inputDir, ...)递归遍历编译产物目录(:105-119)。- 对每个
.class跑AnnotationDetector,若带@ZenExpansion/@ZenClass/@BracketHandler之一则收进classNames(:129-131、:151-161)。 - 若某方法带
@OnRegister且描述符恰为()V,收进onRegisterMethods;签名不符直接抛RuntimeException("OnRegister annotation must be used on a static method without arguments or return value")(:184-189)。 - 用
ClassWriter(COMPUTE_MAXS)生成getClasses(List)方法体:先对每个类ALOAD 0+LDC 类引用+List.add,再对每个@OnRegister方法INVOKESTATIC(:68-79)。 - 写回
minetweaker/mc1710/MineTweakerRegistry.class(:89-96)。
生成的类 extends java/lang/Object、版本 V1_6(:66),即纯 JVM 字节码,不含任何 ZenScript 依赖。
注册调用链
MineTweakerMod.onPostInit(MineTweakerMod.java:140-149):
MineTweakerAPI.registerClassRegistry(MineTweakerRegistry.class);
for (int i = 0; i < REGISTRIES.length; i++) {
MineTweakerAPI.registerClassRegistry(REGISTRIES[i], REGISTRY_DESCRIPTIONS[i]);
}
FuelTweaker.INSTANCE.register();
registerClassRegistry(Class, String)(MineTweakerAPI.java:233-267)反射取 getClasses(List),校验其为 static,调用收集类列表,然后逐个做两件事:
- 门控:若类带
@ModOnly,对注解里每个 mod 名检查loadedMods.contains(mod)与版本前缀(MineTweakerAPI.java:245-256),任一不满足即continue outer跳过该类。 - 分派:
MineTweakerAPI.registerClass(cls)(MineTweakerAPI.java:257)。
成功时打印 Loaded class registry: <description>(MineTweakerAPI.java:260)。
缺陷:5 个字符串注册的 ClassRegistry 全部不存在
MineTweakerMod.java:59-63 声明了 5 个类名与对应描述:
| 索引 | 类名字符串 | 描述 | 类是否存在 |
|---|---|---|---|
| 0 | minetweaker.mods.ic2.ClassRegistry |
IC2 mod support | 不存在 |
| 1 | minetweaker.mods.nei.ClassRegistry |
NEI mod support | 不存在 |
| 2 | minetweaker.mods.mfr.ClassRegistry |
MFR mod support | 不存在 |
| 3 | minetweaker.mods.gregtech.ClassRegistry |
GregTech mod support | 不存在 |
| 4 | minetweaker.mods.buildcraft.ClassRegistry |
Buildcraft mod support | 不存在 |
核实方式:逐个检查 src/main/java/minetweaker/mods/<pkg>/ClassRegistry.java,5 个全部 MISSING;git ls-files | grep -i ClassRegistry 全仓只命中 minetweaker/ClassRegistry.java 一个文件(且其 getClasses 是空壳,见全局符号)。git ls-files src/main/java/minetweaker/mods 下只存在 ic2/ 与 nei/ 两个子目录,没有 mfr/、gregtech/、buildcraft/ 任何文件。
后果:registerClassRegistry(String, String) 的实现是
try {
registerClassRegistry(Class.forName(className), description);
return true;
} catch (ClassNotFoundException ex) {
return false;
}
(MineTweakerAPI.java:289-296)—— 吞掉异常、返回 false,调用方 MineTweakerMod.java:144-146 完全不检查返回值。因此这 5 次循环每次都静默失败,Loaded class registry: 日志一条都不会打印,MFR / GregTech / Buildcraft 集成在本仓没有任何代码。
这不是功能缺失,而是现代重写后的残留常量:IC2 与 NEI 的脚本类已改由 MineTweakerRegistry 自动扫描注册(见IC2 集成),旧的按包拆分注册表不再需要,但数组与描述字符串没有清掉。
扫描顺序不确定性
iterate 用 File.listFiles() 遍历目录(RegisterZenClassesTask.java:106),Java 规范不保证返回顺序。生成的 getClasses 按发现顺序 List.add,而 registerNativeClass 又是后写覆盖(GlobalRegistry.java:94)。这对重名的 minetweaker.item.WeightedItemStack 会产生实际影响,详见类型系统与扩展。
@OnRegister 当前零使用
grep -rE '^\s*@OnRegister\b' src/main/java 命中 0 个文件(注解本身定义在 minetweaker/annotations/OnRegister.java)。生成任务为此实现了一整套机制(RegisterZenClassesTask.java:58-60、:77-79、:132-136、:196-204),目前没有任何类使用它。