类注册机制

基本信息

属性 值
入口类 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 现场合成一个新类:

  1. iterate(inputDir, ...) 递归遍历编译产物目录(:105-119)。
  2. 对每个 .class 跑 AnnotationDetector,若带 @ZenExpansion / @ZenClass / @BracketHandler 之一则收进 classNames(:129-131、:151-161)。
  3. 若某方法带 @OnRegister 且描述符恰为 ()V,收进 onRegisterMethods;签名不符直接抛 RuntimeException("OnRegister annotation must be used on a static method without arguments or return value")(:184-189)。
  4. 用 ClassWriter(COMPUTE_MAXS) 生成 getClasses(List) 方法体:先对每个类 ALOAD 0 + LDC 类引用 + List.add,再对每个 @OnRegister 方法 INVOKESTATIC(:68-79)。
  5. 写回 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,调用收集类列表,然后逐个做两件事:

  1. 门控:若类带 @ModOnly,对注解里每个 mod 名检查 loadedMods.contains(mod) 与版本前缀(MineTweakerAPI.java:245-256),任一不满足即 continue outer 跳过该类。
  2. 分派: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),目前没有任何类使用它。

相关条目