在运行时修改jarfile



我有一个关于java的问题。是否可以在java程序中加载、获取该类中的类和方法、修改方法、重新编译jarfile并覆盖它?

示例:a.jar有一个类,带有static void main((方法,它只打印"世界;

我的目标是加载.jar的主类,获得static void main((方法,并添加另一个对print的调用"你好";在顶部,然后再次将其保存到磁盘。所以现在如果我运行一个.jar,它会说"世界";,然后我运行我的程序,它保存jarfile,然后我再次运行.jar,它说";你好";,那么";世界";。

这样的事情可能发生吗?如果可能,怎么可能?

您可以使用Java的ASM库来完成这项工作。我自己测试过,它运行得很好。

对于较新版本的Java > 8,您必须将exportjdk.internal.org.objectweb.asm连接到当前模块。。或者,如果您不喜欢使用内部类,您可以下载objectweb.asm,将其作为依赖项添加,并以与下面完全相同的方式使用。。

代码:

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.FieldNode;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
public static void main(String[] args) {
Main cls = new Main();
//Add a new field to the class called "Main".
//This field will be called "key" and will have a value of "SomeValue".
if (cls.getSelfValue("key") == null) {
System.out.println(cls.modifySelf("key", "SomeValue")); //Prints true upon success.
System.out.println(cls.getSelfValue("key")); //Prints "SomeValue".
}
else {
System.out.println("Key field already exists with a value of: " + cls.getSelfValue("key"));
}
}
//Load the byte-code for the current class.
private ClassNode getSelf() {
try {
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(this.getClass().getSimpleName());
reader.accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return node;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//Save the byte-code for the current class.
private boolean setSelf(ClassNode node) {
try {
//Create a class writer. use a ClassCheckAdapter on it to make sure we aren't making any mistakes! The adapter checks whether or not our byte-code makes sense.
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
node.accept(new CheckClassAdapter(writer, false));
//Save the byte-code for the current class.
DataOutputStream stream = new DataOutputStream(new FileOutputStream(new File(ClassLoader.getSystemResource(this.getClass().getSimpleName().replace('.', '/') + ".class").getPath())));
stream.write(writer.toByteArray());
stream.flush();
stream.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
//Add a new field of type "String" to the current class. Give it a name and a value.
private boolean modifySelf(String name, String value) {
ClassNode node = this.getSelf(); //Load the byte-code for the current class.
node.fields.add(new FieldNode(Opcodes.ASM4, name, "Ljava/lang/String;", null, value)); //Add a new field to the class.
return setSelf(node);
}
//Get the field with the specified name from the current class. Return its value.
private String getSelfValue(String name) {
ClassNode node = this.getSelf(); //Load the byte-code for the current class.
for (FieldNode f : node.fields) {
if (f.name.equals(name) && f.desc.equals("Ljava/lang/String;")) {
return (String)f.value;
}
}
return null;
}
}

上面的代码添加了一个字段。要modifymain方法,可以做:

for (MethodNode method : mainClassNode.methods) {
if (method.name.equals("main") && method.desc.equals("()V")) {
method.instructions.clear();
method.instructions.add(new LdcInsnNode("Hello"));
method.instructions.add(new VarInsnNode(Opcodes.ASTORE, 0));
method.instructions.add(new TypeInsnNode(Opcodes.NEW, "java/lang/StringBuilder"));
method.instructions.add(new InsnNode(Opcodes.DUP));
method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false));
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
method.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
method.instructions.add(new LdcInsnNode(" - World"));
method.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
method.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false));
method.instructions.add(new VarInsnNode(Opcodes.ASTORE, 0));
//Add more instructions to actually call `System.out.println` on `ALOAD_0` (aka stack variable 0.. aka our `StringBuilder.toString()`)
}
}

最新更新