Skip to main content
该模块提供运行时字节码注入能力,可以在 JVM 运行时动态拦截 Java 方法调用,插入自定义逻辑或完全替换方法实现。这为运行时调试、热修复等场景提供了强大支持。
此功能需要 Java Agent 支持,启动 JVM 时需要添加 -javaagent 参数。详见前置要求

前置要求

1. 环境准备

使用该功能需要 inst-core 模块并手动注册 jvm 模块。
build.gradle.kts
compileOnly("org.tabooproject.fluxon:core:1.4.5")
compileOnly("org.tabooproject.fluxon:inst-core:1.4.5")
Code
FunctionJvm.init(FluxonRuntime.getInstance());

2. 启动 Java Agent

在 JVM 启动参数中添加:
java -javaagent:fluxon-core-agent.jar -jar your-app.jar
或使用 Gradle 运行:
tasks.withType<JavaExec> {
    jvmArgs("-javaagent:build/libs/fluxon-core-agent.jar")
}

3. 导入模块

在 Fluxon 脚本中导入 fs:jvm 模块:
import 'fs:jvm'

快速上手

拦截方法调用(before)

在方法执行前插入逻辑,不影响原方法执行:
import 'fs:jvm'

// 拦截 System.out.println 调用
id = jvm::inject(
    "java.io.PrintStream::println",
    "before",
    |self, message| {
        println("[LOG] " + &message)
        // 返回值被忽略,原方法继续执行
    }
)

替换方法实现(replace)

完全替换方法的原始实现:
import 'fs:jvm'

// 替换某个业务方法的返回值
id = jvm::inject(
    "com.example.UserService::isAdmin",
    "replace",
    |self, userId| {
        // 这里可以访问原方法参数
        println("isAdmin called with userId: " + &userId)
        return true  // 强制返回 true
    }
)

API 详解

inject - 注入拦截器

签名jvm::inject(target, type, handler) -> String 注入一个方法拦截器,返回唯一的注入 ID。 参数
参数类型说明
targetString目标方法,格式为 className::methodNameclassName::methodName(descriptor)
typeString注入类型,可选 "before""replace"
handlerFunction回调函数,签名为 |self, args...| { ... }
目标格式
// 简单格式(匹配所有重载)
"com.example.Foo::bar"

// 带描述符(精确匹配)
"com.example.Foo::bar(Ljava/lang/String;)V"
JVM 方法描述符使用内部格式,例如 (Ljava/lang/String;I)Z 表示接受 Stringint 参数,返回 boolean。 常见类型映射:I=int, J=long, Z=boolean, Ljava/lang/String;=String, V=void。
注入类型
  • "before":在原方法执行前调用 handler,handler 返回值被忽略,原方法继续执行
  • "replace":完全替换原方法,handler 返回值作为方法返回值
Handler 参数
  • 第一个参数 self
    • 实例方法:接收 this 对象
    • 静态方法:接收 null
  • 后续参数:方法的实际参数,按顺序传递
示例
import 'fs:jvm'

// 拦截 Thread.sleep
sleepId = jvm::inject(
    "java.lang.Thread::sleep(J)V",
    "before",
    |self, millis| {
        println("Thread will sleep for " + &millis + "ms")
    }
)

// 替换 Math.random
randomId = jvm::inject(
    "java.lang.Math::random",
    "replace",
    |self| {
        return 0.42  // 固定返回值
    }
)

restore - 撤销注入

签名jvm::restore(idOrTarget) -> Boolean 撤销一个或多个注入,返回是否成功。 参数
  • 传入注入 ID:撤销指定的单个注入
  • 传入目标字符串(包含 ::):撤销该方法的所有注入
示例
import 'fs:jvm'

id = jvm::inject("com.example.Foo::bar", "before", |self| {})

// 按 ID 撤销
jvm::restore(&id)  // true

// 按目标撤销(删除该方法的所有注入)
jvm::restore("com.example.Foo::bar")  // true

injections - 列出所有注入

签名jvm::injections() -> List<Map> 返回当前所有活跃的注入信息。 返回值:每个 Map 包含以下字段:
字段类型说明
idString注入 ID
targetString目标方法(格式:className::methodName
typeString注入类型("before""replace"
示例
import 'fs:jvm'

jvm::inject("com.example.Foo::bar", "before", |self| {})
jvm::inject("com.example.Foo::baz", "replace", |self| { return null })

list = jvm::injections()
for item in &list {
    println(&item.id + " -> " + &item.target + " (" + &item.type + ")")
}

// 输出:
// inj_1 -> com.example.Foo::bar (before)
// inj_2 -> com.example.Foo::baz (replace)

实战场景

调试与日志

在不修改源码的情况下记录方法调用:
import 'fs:jvm'

// 追踪所有 HTTP 请求
jvm::inject(
    "com.example.api.RequestHandler::handle",
    "before",
    |self, request| {
        println("[HTTP] " + &request.method + " " + &request.uri)
    }
)

热修复

运行时修复生产环境 bug:
import 'fs:jvm'

// 修复错误的计算逻辑
jvm::inject(
    "com.example.billing.Calculator::computeDiscount",
    "replace",
    |self, price, rate| {
        // 修复:原实现忘记除以 100
        return &price * &rate / 100.0
    }
)

条件拦截

只在特定条件下拦截:
import 'fs:jvm'

// 仅拦截特定用户的请求
jvm::inject(
    "com.example.UserController::getProfile",
    "before",
    |self, userId| {
        if &userId == "admin" {
            println("[AUDIT] Admin profile accessed")
        }
    }
)

注意事项

类加载时机

  • 已加载的类:注入会触发 retransformClasses 立即生效
  • 未加载的类:注入会在类首次加载时自动应用

方法签名

使用带描述符的目标格式可以精确匹配重载方法:
// 只拦截 println(String)
jvm::inject("java.io.PrintStream::println(Ljava/lang/String;)V", "before", ...)

// 只拦截 println(int)
jvm::inject("java.io.PrintStream::println(I)V", "before", ...)
不带描述符时会匹配所有同名方法,可能导致意外行为。生产环境建议始终使用完整签名。

限制

  • 不支持注入 JVM 核心类(如 java.lang.Object)的 native 方法
  • 不支持注入构造函数(<init><clinit>
  • 注入点过多可能影响类加载性能

调试技巧

查看当前注入

import 'fs:jvm'

for injection in jvm::injections() {
    println(&injection.id + ": " + &injection.target + " [" + &injection.type + "]")
}

清理所有注入

import 'fs:jvm'

for injection in jvm::injections() {
    jvm::restore(&injection.id)
}

相关链接