Skip to content

这是一个基于字节码操作(如 ASM)的轻量级 Mixin 框架,旨在运行时动态修改、增强 Java 类的行为。

Notifications You must be signed in to change notification settings

tzdwindows/TzdMixin

Repository files navigation

这份文档为您详细介绍 org.tzd.mixin 包下的注解框架。这是一个基于字节码操作(如 ASM)的轻量级 Mixin 框架,旨在运行时动态修改、增强 Java 类的行为。


TZD Mixin 框架开发文档

1. 简介

org.tzd.mixin 提供了一套注解 API,允许开发者通过编写 "Mixin 类" 来修改目标类(Target Class)的字节码。它支持方法注入(Inject)、全能拦截(UniversalInject)以及字段/方法的影子引用(Shadow)。

主要特性:

  • 非侵入式:无需修改目标类的源代码。
  • 通配符支持:支持通过 * 批量监控方法。
  • 生命周期管理:支持在方法执行前(HEAD)和返回前(RETURN)插入逻辑。

2. 核心注解详解

2.1 @Mixin

目标:类 (Type)
作用:标记当前类为一个 Mixin 类,并指定要增强的目标类。

属性 类型 必须 描述
value String 目标类的全限定名(例如 "java.lang.Thread""com.example.User")。

示例

@Mixin("org.tzd.test.Player")
public class PlayerMixin { ... }

2.2 @UniversalInject

目标:方法 (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)

2.3 @Inject

目标:方法 (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!");
}

2.4 @Shadow

目标:方法 (Method) / 字段 (Field)
作用:占位符注解。用于在 Mixin 类中引用目标类由于访问权限(private/protected)或编译隔离而不可见的成员。

  • 带有 @Shadow 的成员不会被注入到目标类中。
  • 它们仅用于让 Mixin 代码能通过编译,并在字节码转换时自动映射到目标类的真实成员。

示例

@Shadow
private int health; // 对应目标类的 private int health

3. 开发示例

场景一:全能日志监控 (UniversalInject)

监控 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,否则目标逻辑会被破坏
    }
}

场景二:修改业务逻辑与访问私有字段 (Inject & Shadow)

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);
    }
}

4. 注意事项与最佳实践

  1. JDK 核心类限制

    • 针对 java.lang.Thread 等由 Bootstrap ClassLoader 加载的核心类,严禁修改类结构(如添加新字段、新方法)。
    • 对于核心类,建议仅使用 @UniversalInject 修改现有方法体,或使用 @Inject 注入代码,不要编写导致 Mixin 试图向目标类添加普通方法或字段的代码。
  2. 死循环风险

    • 如果在 @UniversalInject 的实现中调用了 System.out.println,而你同时监控了 java.io.PrintStreamjava.lang.String,会导致无限递归(StackOverflowError)。
    • 建议:在 Mixin 逻辑中使用 ThreadLocal 锁进行防重入保护。
  3. 方法签名匹配

    • @UniversalInject 对方法的参数顺序非常敏感。请严格按照文档中的签名编写 startMethodendMethod,否则会导致 VerifyErrorNoSuchMethodError
  4. 混合使用

    • 同一个 Mixin 类中可以同时包含 @Inject@UniversalInject
    • Mixin 类本身的方法(未加注解的普通方法)会被尝试复制到目标类中。如果目标类已存在同名同签名方法,通常会跳过或覆盖(取决于具体实现策略)。

About

这是一个基于字节码操作(如 ASM)的轻量级 Mixin 框架,旨在运行时动态修改、增强 Java 类的行为。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published