如何在 Java 运行时重新加载修改过的类?

问题描述 投票:0回答:1
我正在写关于 Java 运行时代码操作库的学士论文。我必须为实际部分开发一个探查器,它可以将代码注入到用注释标记的加载类中。我正在使用新的 Java ClassFile API,一切都按计划进行,直到我希望我的更改生效。我已经转换了类中某些方法的方法体(包括计时器等来测量函数需要多长时间等),现在我有一个包含修改后的类的字节数组。现在的问题是我不知道如何加载这个新类并使其更改影响修改后的类的所有后续实例。

Test before = new Test(); before.doNothing(); Profiler profiler = Profiler.getInstance(); profiler.inject(); Test after = new Test(); after.doNothing();
这是测试代码。 Test 类有一个函数,实际上什么也不做,字节码只是一个返回。探查器注入打印 20 的打印语句的字节码。但它不会受到任何更改的影响。我尝试使用自定义类加载器加载它,并且我能够创建该自定义加载类的新实例并打印 20,但这并没有覆盖实际的 Test 类,而是创建了一个额外的类,并且该类的 doNothing实际的测试类仍然相同,什么也不做。因此,我知道修改本身是有效的,只是没有正确应用。我需要一种方法以某种方式使用字节数组中的新类(如果有帮助的话也可以将其写入临时文件中)以某种方式全局更新类,并且它应该在运行时发生,没有实际的覆盖或编译时的东西.

java class jvm classloader
1个回答
0
投票
如果您只想转换方法的主体,那么您可以通过

instrumentation 来完成。这是一个例子:

  • Greeter#greet()

     方法转换为打印 
    "Goodbye, World!"
     而不是 
    "Hello, World!"

  • 使用 Java 22 类文件 API 预览功能来转换方法体(与您相同)。

  • 使用

    Launcher-Agent-Class

     注册代理,这是特定于可执行 JAR 的。请参阅之前链接的文档以获取替代方案。

请注意,重新转换会影响重新转换之前创建的类的实例。

源代码

Greeter.java

package com.example; public class Greeter { public void greet() { System.out.println("Hello, World!"); } }

Main.java

package com.example; public final class Main { public static void main(String[] args) { var greeter = new Greeter(); greeter.greet(); ExampleAgent.retransformGreeterClass(); greeter.greet(); } }

示例Agent.java

package com.example; import static java.lang.classfile.ClassTransform.transformingMethodBodies; import java.lang.classfile.ClassFile; import java.lang.classfile.CodeBuilder; import java.lang.classfile.CodeElement; import java.lang.classfile.Instruction; import java.lang.classfile.MethodModel; import java.lang.classfile.Opcode; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; public class ExampleAgent { private static Instrumentation inst; public static void agentmain(String agentArgs, Instrumentation inst) { ExampleAgent.inst = inst; } public static void retransformGreeterClass() { inst.addTransformer(new GreeterClassFileTransformer(), true); try { inst.retransformClasses(Greeter.class); } catch (UnmodifiableClassException ex) { throw new RuntimeException(ex); } } private static class GreeterClassFileTransformer implements ClassFileTransformer { @Override public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if ("com/example/Greeter".equals(className)) { var cf = ClassFile.of(); return cf.transform( cf.parse(classfileBuffer), transformingMethodBodies(this::isGreetMethod, this::acceptGreetMethodElement)); } return null; } private boolean isGreetMethod(MethodModel model) { return model.methodName().equalsString("greet"); } private void acceptGreetMethodElement(CodeBuilder builder, CodeElement element) { if (element instanceof Instruction i && i.opcode() == Opcode.LDC) { builder.ldc("Goodbye, World!"); } else { builder.with(element); } } } }
清单

清单.MF

Main-Class: com.example.Main Launcher-Agent-Class: com.example.ExampleAgent Can-Retransform-Classes: true
项目目录

<PROJECT-DIR> | \---src +---com | \---example | ExampleAgent.java | Greeter.java | Main.java | \---META-INF MANIFEST.MF
构建与执行

工作目录是

<PROJECT-DIR>

编译:

javac --enable-preview --release 22 --source-path src -d out/classes src/com/example/*.java
包装:

jar cfm out/example.jar src/META-INF/MANIFEST.MF -C out/classes .
执行:

java --enable-preview -jar out/example.jar
输出

Hello, World! Goodbye, World
    
© www.soinside.com 2019 - 2024. All rights reserved.