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

时间:2018-07-20 04:28:54

标签: java reflection

我当前正在尝试在运行时中编辑类文件,例如:

Example.java使用以下代码:

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

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

1 个答案:

答案 0 :(得分:0)

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

最简单的方法是在加载该类之前警告该类,并迫使JVM加载您的版本,但是随后必须由适当的ClassLoader来加载它,以您的昵称我可以假设您正在尝试使用Spigot进行一些魔术我的世界引擎?然后,如果您想从其他插件更改类,您要做的就是将此类实际复制到您的项目中,并将该类加载到主类的静态块中-只需确保您的插件将在其他插件之前加载即可。 br /> 这将导致JVM在原始类之前加载该类,并且由于spigot类加载器的工作方式-它将被添加到插件类的全局映射中,因此不会加载具有该名称的其他类,而其他插件将使用您的类代替。
这在其他地方也是可行的,不仅是插销-而且不是在每个应用程序中,因为它必须具有类似的类加载-并共享存储要编辑的plugin / jar和plugin / 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文件,并允许您对其进行编辑,然后将其注入到选定的类加载器中(我认为它默认是系统加载器,您也可以使用.toClass(ClassLoader)方法)
此技巧最重要的部分是无法在执行此代码之前加载类。这就是为什么您需要手动提供类名,而从不做类似MyClass.class.getName()的事情,这将打破这个窍门。
请注意,javassist Java编译器有些棘手且受限制,请参阅其网页以找到更多信息和有用的技巧。

如果已经加载了类,那么您有最后的选择。
您可以通过Instrumentation类使用Java代理来完成此操作-因为它允许在运行时重新加载类(有一些限制,就像调试器一样,您不能更改类架构,但是可以使用{{ 1}}之前加载)。

因此,您需要创建一个特殊的Java代理,以使用https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses(java.lang.instrument.ClassDefinition...)

在此类加载之前或之后对其进行编辑(但有少量限制)

但是通常情况下,您需要在可运行的.jar中添加特殊标志或清单条目,如果要在JDK上启动此代码,则要容易得多-您可以通过一些技巧创建代理并将其附加到VM在运行时,需要一些代码,因此我只建议使用ClassFileTransformer库,然后可以使用单行代码进行检测:

byte-buddy-agent

您可以重新定义所有想要的类,在您的情况下,我还建议使用Javassist库来编辑代码-因为它是最简单的可用代码,但是对于Javassist,您也可以使用ByteBuddy或raw ASM。看起来像这样:

Instrumentation install = ByteBuddyAgent.install();

如果已经加载了类,并且您在JRE上,并且由于某些原因您无法更改应用程序启动参数,那么任何一种方法都不可能-编写注释并描述方式-我知道其他一些魔术,但是将需要更多时间来描述它,所以我需要更多信息。