这份文档为您详细介绍 org.tzd.mixin 包下的注解框架。这是一个基于字节码操作(如 ASM)的轻量级 Mixin 框架,旨在运行时动态修改、增强 Java 类的行为。
org.tzd.mixin 提供了一套注解 API,允许开发者通过编写 "Mixin 类" 来修改目标类(Target Class)的字节码。它支持方法注入(Inject)、全能拦截(UniversalInject)以及字段/方法的影子引用(Shadow)。
主要特性:
- 非侵入式:无需修改目标类的源代码。
- 通配符支持:支持通过
*批量监控方法。 - 生命周期管理:支持在方法执行前(HEAD)和返回前(RETURN)插入逻辑。
目标:类 (Type)
作用:标记当前类为一个 Mixin 类,并指定要增强的目标类。
| 属性 | 类型 | 必须 | 描述 |
|---|---|---|---|
value |
String |
是 | 目标类的全限定名(例如 "java.lang.Thread" 或 "com.example.User")。 |
示例:
@Mixin("org.tzd.test.Player")
public class PlayerMixin { ... }目标:方法 (Method)
作用:一种高级拦截器,以静态回调的方式监控目标方法的执行。它不直接把代码复制进目标方法,而是插入对 Mixin 方法的静态调用。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
method |
String |
"*" |
目标方法名,支持简单的通配符(如 "get*" 或 "*")。 |
方法签名要求: 被标记的方法必须遵循特定的参数签名,否则运行时会报错或栈崩溃。
- 前置拦截 (Start/Entering):方法名通常包含 "start"。
public static void startMethod(Class<?> clazz, String methodName, String desc, Object obj, Object... args)
- 后置拦截 (End/Exiting):方法名通常包含 "end"。必须有返回值(通常返回
ret)。public static Object endMethod(Class<?> clazz, String methodName, String desc, Object obj, Object ret, boolean isReturn, Object... args)
目标:方法 (Method)
作用:将 Mixin 方法中的字节码指令直接复制并插入到目标类的指定方法中。
| 属性 | 类型 | 必须 | 描述 |
|---|---|---|---|
method |
String |
是 | 确切的目标方法名。 |
at |
At |
否 | 注入位置,默认为 At.HEAD。 |
枚举 At 选项:
At.HEAD:方法的最开始(构造函数中会在super()之后)。At.RETURN:方法所有的return指令之前。
示例:
@Inject(method = "calculate", at = At.HEAD)
public void onCalculate() {
System.out.println("Calculate called!");
}目标:方法 (Method) / 字段 (Field)
作用:占位符注解。用于在 Mixin 类中引用目标类由于访问权限(private/protected)或编译隔离而不可见的成员。
- 带有
@Shadow的成员不会被注入到目标类中。 - 它们仅用于让 Mixin 代码能通过编译,并在字节码转换时自动映射到目标类的真实成员。
示例:
@Shadow
private int health; // 对应目标类的 private int health监控 org.tzd.test.Player 类中所有以 get 开头的方法。
package org.tzd.mixin.test;
import org.tzd.mixin.Mixin;
import org.tzd.mixin.UniversalInject;
@Mixin("org.tzd.test.Player")
public class PlayerMonitorMixin {
// 监控方法进入
@UniversalInject(method = "get*")
public static void onStart(Class<?> clazz, String methodName, String desc, Object obj, Object... args) {
System.out.println(">>> Calling getter: " + methodName);
}
// 监控方法退出,并打印返回值
@UniversalInject(method = "get*")
public static Object onEnd(Class<?> clazz, String methodName, String desc,
Object obj, Object ret, boolean isReturn, Object... args) {
System.out.println("<<< Getter finished: " + methodName + ", result: " + ret);
return ret; // 必须返回 ret,否则目标逻辑会被破坏
}
}在 Player 受伤 (damage) 时,强制减少伤害值,并打印当前的生命值(私有字段)。
package org.tzd.mixin.test;
import org.tzd.mixin.Mixin;
import org.tzd.mixin.Inject;
import org.tzd.mixin.Shadow;
import org.tzd.mixin.At;
@Mixin("org.tzd.test.Player")
public class PlayerLogicMixin {
// 1. 使用 Shadow 映射目标类的私有字段 'hp'
@Shadow
private int hp;
// 2. 注入代码到 damage 方法的头部
@Inject(method = "damage", at = At.HEAD)
public void modifyDamage() {
// 这里的 this 在运行时会指向目标对象 (Player)
System.out.println("Current HP before damage: " + this.hp);
// 注意:@Inject 只能插入代码,如果你想修改参数,通常需要更复杂的 ASM 操作
// 这里的示例仅展示逻辑插入
}
// 3. 在方法返回前注入
@Inject(method = "damage", at = At.RETURN)
public void afterDamage() {
System.out.println("Remaining HP: " + this.hp);
}
}-
JDK 核心类限制:
- 针对
java.lang.Thread等由 Bootstrap ClassLoader 加载的核心类,严禁修改类结构(如添加新字段、新方法)。 - 对于核心类,建议仅使用
@UniversalInject修改现有方法体,或使用@Inject注入代码,不要编写导致 Mixin 试图向目标类添加普通方法或字段的代码。
- 针对
-
死循环风险:
- 如果在
@UniversalInject的实现中调用了System.out.println,而你同时监控了java.io.PrintStream或java.lang.String,会导致无限递归(StackOverflowError)。 - 建议:在 Mixin 逻辑中使用
ThreadLocal锁进行防重入保护。
- 如果在
-
方法签名匹配:
@UniversalInject对方法的参数顺序非常敏感。请严格按照文档中的签名编写startMethod和endMethod,否则会导致VerifyError或NoSuchMethodError。
-
混合使用:
- 同一个 Mixin 类中可以同时包含
@Inject和@UniversalInject。 - Mixin 类本身的方法(未加注解的普通方法)会被尝试复制到目标类中。如果目标类已存在同名同签名方法,通常会跳过或覆盖(取决于具体实现策略)。
- 同一个 Mixin 类中可以同时包含