类注入与 include
OpenModsClassTransformer.transform 的另外两个分支构成 OpenModsLib 的通用类操作能力:一条为运行时动态生成类提供字节码,一条为注解驱动的接口混入改写第三方类。两者都作用在 coremod 阶段,机制见coremod 补丁。
基本信息
| 属性 | 值 |
|---|---|
| 动态类管理器 | openmods.injector.InjectedClassesManager |
| 生成类名前缀 | $✗$(GENERATED_CLS_PREFIX,InjectedClassesManager.java:11) |
| 分隔符 | ☞(GENERATED_CLS_SEPARATOR,InjectedClassesManager.java:13) |
| include 变换器 | openmods.include.IncludingClassVisitor(287 行) |
| include 注解 | @IncludeInterface、@IncludeOverride |
| 已废弃标记接口 | openmods.include.IExtendable(@Deprecated) |
| 崩溃报告条目 | InjectorSanityChecker(标签 "Class transformer null safety") |
功能
动态类生成
InjectedClassesManager(src/main/java/openmods/injector/InjectedClassesManager.java:9)用「魔法类名」协议在类加载时按需合成字节码。
类名协议(InjectedClassesManager.java:11,13,32):
$<U+2697>$$<providerId><U+261E><arg> → 实际显示为 $✗$providerId☞arg
- 前缀
$✗$之后的类不要求真实存在,transform收到bytes == null时才走这条路 providerId标识生成者,arg是传给生成器的参数createClassName(providerId, arg)(:26)负责拼装,providerId未注册时Preconditions.checkState直接失败
tryGetBytecode(:32)的解析与降级路径:
| 情况 | 行为 | 行 |
|---|---|---|
类名不以 $✗$ 开头 |
返回 null(交回正常加载流程) |
:33 |
| 按分隔符切分后不是 2 段 | Log.warn("Malformed generated class: %s") 后返回 null |
:35-38 |
providerId 未注册 |
Log.warn("Unknown provider: %s") 后返回 null |
:41-44 |
| 生成器抛异常 | Log.severe 后返回 null |
:50-53 |
全部失败路径都返回 null 而非抛出,让 JVM 报标准的 ClassNotFoundException —— 这是有意的降级设计,代价是真实错误原因只进日志。registerProvider(:20)对重复 providerId 直接 checkState 失败。
GENERATED_CLS_PREFIX 与 GENERATED_CLS_SEPARATOR 在源码中以 "\u2697" / "\u261E" 转义形式书写(:11,13),而非字面字符。
include:注解驱动的接口混入
这是 OpenModsLib 最有特色的字节码能力:让任何类(第三方 mod 的类也一样)通过一个注解就自动实现指定接口并生成全部转发方法。
注解(src/main/java/openmods/include/):
| 注解 | 目标 | 值 | 作用 |
|---|---|---|---|
@IncludeInterface |
FIELD 或 METHOD |
Class<?>,默认 Object.class |
被注解的字段类型 / 方法返回类型需实现的接口 |
@IncludeOverride |
仅 METHOD |
无 | 声明本方法已手动实现,用于消解冲突 |
变换流程(IncludingClassVisitor,src/main/java/openmods/include/IncludingClassVisitor.java:28):
visit(:239)记录类元信息;若目标是接口则立即throw new StopTransforming()(:240)—— 接口不被处理visitField(:195)与visitMethod(:201)各返回一个FieldVisitor/MethodVisitor包装器,在visitAnnotation中识别注解描述符(:45,79,80)- 注解值经
IncludeAnnotationVisitor.visit(:124)读出接口类型,addInterfaceImplementations(:274)把接口加入类的接口列表,并用getInterfaceMethods(:253)反射取其全部方法 MethodAdder.addMethod(:145)为每个方法合成字节码:ACC_PUBLIC | ACC_SYNTHETIC方法 →ALOAD 0→ 取接口引用 →CHECKCAST→ 逐参数ILOAD→INVOKEINTERFACE→IRETURN(:147-172)visitEnd(:210)做三重一致性校验后再写回类
三重校验(IncludingClassVisitor.java:211-227)是这套机制的安全阀:
| 校验 | 失败信息 | 行 |
|---|---|---|
已有方法与待加方法冲突、且冲突方法未标 @IncludeOverride |
"%s implements interface methods %s, but they are not marked with @IncludeOverride" |
:212-217 |
标了 @IncludeOverride 但没有接口提供该方法 |
"%s marks methods %s with @IncludeOverride, but no interface implements it" |
:219-224 |
| 同一方法被两个接口同时提供 | "Included method '%s' conflict, interfaces = %s,%s"(addInterfaceImplementations 内) |
:281-287 |
三类失败均为 Preconditions.checkState,即直接抛异常终止类变换,不会静默生成错误字节码。
visitEnd 末尾的 super.visit 注释写明 // risky, but should work, since we are only replacing interfaces(:237)—— 它整表替换接口数组而非增量追加。
生效范围
OpenModsClassTransformer.shouldTryIncluding(src/main/java/openmods/core/OpenModsClassTransformer.java:239)决定哪些类进入 include 流程:
injectAsmData已执行(正常游戏流程):只处理 FML 在ASMDataTable中扫描到的带@IncludeInterface/@IncludeOverride的类(白名单,OpenModsClassTransformer.java:230-235收集)- 尚未执行:走
IGNORED_PREFIXES黑名单(8 个前缀,见coremod 补丁)
这意味着正常游戏中该变换几乎不改动任何类,只处理显式标注的类。
变换器安全检查
InjectorSanityChecker(src/main/java/openmods/injector/InjectorSanityChecker.java:16)作为 ICrashCallable 注册进崩溃报告(OpenModsCore.java:54),用于检测其它 mod 的 coremod 是否遵守 IClassTransformer.transform 的契约。
检查逻辑(findUnsafeTransformers,:38)遍历 LaunchWrapper 的全部变换器,对每个变换器传入一个随机假类名并 bytes = null:
| 变换器行为 | 判定 | 记录内容 | 行 |
|---|---|---|---|
变换器本身为 null |
不安全 | <null> |
:41 |
对 bytes == null 返回非 null 字节 |
不安全 | returned non-null result: <长度> |
:45-52 |
| 抛异常 | 不安全 | crashed with <异常类>(<消息>) |
:54-62 |
| 全部安全 | 安全 | "all safe" |
:31 |
transform 契约要求传入 null 字节时必须返回 null(表示「无变换」)。不遵守的第三方变换器会破坏 InjectedClassesManager 的动态类生成 —— 这就是该检查器存在的原因。
数值
| 数值名 | 值 |
|---|---|
injector 包类数 |
3(IClassBytesProvider、InjectedClassesManager、InjectorSanityChecker) |
include 包类数 |
4(IExtendable、IncludeInterface、IncludeOverride、IncludingClassVisitor) |
| include 注解数 | 2 |
| 生成类名中的特殊字符数 | 2(前缀 $✗$、分隔符 ☞) |
tryGetBytecode 的降级返回路径数 |
4(:33,35,41,50) |
visitEnd 的一致性校验数 |
3 |
| 被 include 变换直接跳过的目标 | 全部接口类(IncludingClassVisitor.java:240) |
| 崩溃报告中检查的变换器来源 | LaunchWrapper 全局列表(InjectorSanityChecker.java:40) |
交互
无玩家可见交互。两处诊断入口均在崩溃报告中:"Class transformer null safety"(本条目)和 "OpenModsLib class transformers"(见coremod 补丁)。
applyIncludes(OpenModsClassTransformer.java:254)在 include 失败时 Log.severe 后重新抛出(:259),即 include 失败会让游戏启动崩溃而非降级 —— 这与 InjectedClassesManager 的静默降级策略形成对比,前者是硬失败,后者是软失败。
已废弃 API
IExtendable(src/main/java/openmods/include/IExtendable.java:7)是一个空接口,带 @Deprecated 与注释:
/**
* @deprecated No longer needed, annotations are sufficient
*/
它已无任何实际作用,IncludingClassVisitor 中不含对它的引用。检索 include 机制时不应把它当作有效入口。
相关条目
- coremod 补丁 —
transform()的分支顺序与 6 个原版补丁 - 指令总览 —
/om_source_*可查类的加载来源