Java反映,在运行时编辑代码

问题描述 投票:0回答:1

我目前正在尝试在运行时编辑一个类文件,例如:

带有此代码的Example.java:

public static void execute(){
System.out.println("hello worl");
}

在这个例子中没有简单的方法来编辑文本,现在我需要编写“hello worl”到“hello world”的代码而无需访问Example.java并且无需重新启动程序来编辑字节代码,这可能吗?我搜索了很多文章,但没有找到明确的答案。

java reflection
1个回答
0
投票

这取决于您拥有多少访问权限。

这种最简单的方法是在加载之前警告该类并强制JVM加载它的版本,但是它必须由适当的ClassLoader加载,通过你的昵称我可以假设你试图使用Spigot minecraft引擎做一些魔术?然后,如果你想从其他插件中更改类,你需要做的就是将这个类实际复制到你的项目并在主类的静态块中加载这个类 - 只需确保你的插件在另一个之前加载。 这将导致JVM在原始类之前加载此类,并且由于spigot类加载器的工作方式 - 它将被添加到插件类的全局映射中,因此将不会加载具有该名称的其他类,而其他插件将使用您的类代替。 这在其他地方也是可能的,不仅仅是插头 - 而是在每个应用程序中都没有,因为它必须具有类似的类加载 - 您要编辑的插件/ jar和插件/ jar的类的共享存储。

其他方法是使用Javassist库来执行类似但在运行时的操作:

    ClassPool classPool = ClassPool.getDefault();
    CtClass ctToEdit = classPool.getCtClass("my.class.to.Edit");
    CtMethod execute = ctToEdit.getDeclaredMethod("execute");
    execute.setBody("{System.out.println(\"hello world\");}");
    ctToEdit.toClass(); // force load that class

Javassist将为该类找到.class文件并允许您编辑它,然后将其注入选定的类加载器(我认为默认情况下它是系统1,您也可以使用.toClass(ClassLoader)方法) 这个技巧最重要的部分是在执行此代码之前无法加载类。这就是为什么你需要手动提供类名,永远不要做像MyClass.class.getName()这样的事情,这将打破这个伎俩。 请注意,javassist java编译器有点棘手且有限,请参阅他们的网页以查找更多信息和有用的技巧。

如果已经加载了类...那么你有最后一个选项。 您可以通过Instrumentation类使用java代理来实现它 - 因为它允许在运行时重新加载类(几乎没有限制,就像调试器一样,您不能更改类模式,但您可以在加载类之前使用ClassFileTransformer更改任何内容) 。

所以你需要创建一个特殊的java代理,它将在加载之前编辑这个类,或者在加载之后编辑这个类(但是后面有一些限制)使用https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses(java.lang.instrument.ClassDefinition...)

但通常这样做你需要为runnable .jar添加特殊标志或清单条目,如果你在JDK上启动这个代码,那么它就容易多了 - 只需很少的技巧就可以创建代理并在运行时将它附加到你的VM,有一些代码需要,所以我建议使用byte-buddy-agent库,然后你可以获得单行代码的检测:

Instrumentation install = ByteBuddyAgent.install();

您可以根据需要重新定义类,在您的情况下我也建议使用Javassist库来编辑代码 - 因为它是最简单的可用代码,但您也可以使用ByteBuddy或原始ASM,对于javassist,您的代码看起来像这个:

    Instrumentation instrumentation = ByteBuddyAgent.install();
    CtClass ctClass = ClassPool.getDefault().getCtClass(Main.class.getCanonicalName());
    ctClass.defrost(); // as this class is already loaded, javassist tries to protect it.
    CtMethod execute = ctClass.getDeclaredMethod("execute");
    execute.setBody("{System.out.println(\"hello world\");}");
    ClassDefinition classDefinition = new ClassDefinition(Main.class, ctClass.toBytecode());
    instrumentation.redefineClasses(classDefinition);

如果类已经加载并且你在JRE上并且由于某些原因你无法更改应用程序启动参数 - 所以只有任何一种方法都不可能 - 写评论和描述方式 - 我知道其他一些魔法,但它需要很多有更多时间来描述它,所以我需要更多信息。

© www.soinside.com 2019 - 2024. All rights reserved.