转换器注册表(Transformer Registry)

基本信息

属性 值
类型 字节码转换器注册表
实现类 net.vfyjxf.nechar.transform.TransformerRegistry
来源 基于 Towdium 的 JustEnoughCharacters 1.12.0 版 TransformerRegistry.java 改写(MIT 协议)
转换器总数 2 个(均由 static 块注册)

注册顺序(static 块)

configurables.add(new TransformerString());
configurables.add(new TransformerRegExp());
transformers.addAll(configurables);
顺序 转换器 类型 getName()
1 TransformerString Transformer.Configurable string contains
2 TransformerRegExp Transformer.Configurable regular expression
  • configurables 与 transformers 是两个公开静态列表;两者内容当前完全相同,transformers 由 configurables 一次性拷贝而来。
  • 顺序有意义:NechClassTransformer#transform 按 transformers 的迭代顺序依次应用转换器。TransformerString 先于 TransformerRegExp,因为源码注释明确写着匹配到方法名时 break(每个方法每类只收集首个命中),而两个转换器的目标方法集合存在重叠类。

查询接口

getTransformer(String name):遍历 transformers,收集所有 accepts(name) 为 true 的转换器,返回新 ArrayList(可能为空,也可能含多个)。参数 name 是类名(内部名或全限定名,由调用方传入),不是方法名。

Transformer.Configurable 的工作流

两个转换器的共同基类,定义了"配置驱动"的方法改写流程:

阶段 实现 说明
构造 TransformerString() / TransformerRegExp() 各自在构造器中调用 reload() 保证注册表静态块执行时方法表已就绪
reload() 新建 MethodDecoder → addAll(getDefault()) → addAll(getAdditional()) → removeAll(NechConfig.transformerMethodBlackList) → 赋值给 md 默认列表 + 附加列表 − 黑名单
accepts(name) md.contains(name) 仅当该类名在方法表中出现才返回 true
transform(ClassNode c) md.getMethodsForClass(c.name.replace('/', '.')) 取出该类需改写的方法名集合;非空则对 c.methods 中名字命中的方法逐个 transform(MethodNode);为空则打印 No function matched in class ... 方法名匹配不含描述符过滤

transform(byte[])(继承自 Transformer.Default)用 ASM ClassReader 解析字节码 → transform(ClassNode) → ClassWriter(COMPUTE_MAXS) 写回。

MethodDecoder(方法表结构)

基于 Guava HashMultimap<String, String>:类全限定名 → 方法名集合。

方法 行为
addAll(String[] names) 每项按 ":" 分割;必须恰好 2 段(类名:方法名)才 put,否则调 logError 打印 Invalid config syntax: ... 并跳过
removeAll(String[] names) 同上格式校验后 remove(类名, 方法名)
getMethodsForClass(String c) methods.get(c)
contains(String s) methods.containsKey(s)

因为是 HashMultimap(set 语义),列表中的重复项会被自动去重(见 默认转换目标列表)。

ASM 改写原语(Transformer 接口静态方法)

方法 作用
findMethod(ClassNode, name) / findMethod(ClassNode, name, desc) 按名字 / 名字+描述符查找方法并打印日志;本仓两个转换器未直接使用
transformInvoke(MethodNode, owner, name, newOwner, newName, id, isInterface, op, arg1, arg2) 遍历方法指令流,把 INVOKEVIRTUAL / INVOKESPECIAL / INVOKESTATIC 中 owner+name 命中的调用替换为新的 MethodInsnNode;另外处理 INVOKEDYNAMIC——当 arg1/arg2 均非 null 且 bsmArgs[1] 是 Handle 且其 owner/name 命中时,用新的 Handle(6, newOwner, newName, id) 重建 InvokeDynamicInsnNode
transformConstruct(MethodNode, desc, destNew) 改写 NEW 与 INVOKESPECIAL 的类型/属主,用于替换 new X() 的目标类
transformHook(MethodNode, owner, name, id) 在方法内每条 RETURN 指令前插入一次静态调用

所有原语都会把改写动作写入日志(Transforming invoke of ...、Transformed N occurrences. 等),可在开启 verbose 后观察。

相关条目