我目前正在尝试在 JVM 加载 Java 类之后编辑它的字节码。
我使用 Java 8 和 ASM 5.0.3。 我无法更改命令行或 JVM 参数。这是我正在尝试做的一个最小示例:
import org.objectweb.asm.*;
// Is in a library
class ExampleObject {
public void exampleMethod(Object o) {
System.out.println("Body " + o);
}
}
public class Example {
// Comes from the same library
public static final ExampleObject exampleObject = new ExampleObject();
public static void main(String[] args) {
exampleObject.exampleMethod(1);
// Output:
// Body 1
injectAtHead();
exampleObject.exampleMethod(2);
// Output:
// Head 2
// Body 2
}
public static void injectAtHead() {
// Inject methodToInject at the head of exampleObject#exampleMethod
}
public static void methodToInject(Object o) {
System.out.println("Head " + o);
}
}
经过大量研究,我发现了很多关于使用 ASM 动态修改字节码的主题。
问题是他们都讨论在 JVM 加载类之前修改它的字节码。
所以,我不知道如何才能做到这一点,甚至不知道是否有可能。
这个评论几乎回答了问题。
经过几次适应,最初问题的解决方案是:
import net.bytebuddy.agent.ByteBuddyAgent;
import org.objectweb.asm.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import static net.bytebuddy.jar.asm.Opcodes.*;
public class Example {
// Also comes from a library
public static final ExampleObject exampleObject = new ExampleObject();
public static void main(String[] args) {
exampleObject.exampleMethod(1);
// Output:
// Body 1
injectAtHead();
exampleObject.exampleMethod(2);
// Output:
// Head 2
// Body 2
}
public static void injectAtHead() {
try {
ByteBuddyAgent.install();
Instrumentation instrumentation = ByteBuddyAgent.getInstrumentation();
instrumentation.addTransformer(new SimpleClassFileTransformer(), true);
instrumentation.retransformClasses(ExampleObject.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void methodToInject(Object o) {
System.out.println("Head " + o);
}
}
class ModifierMethodWriter extends MethodVisitor {
private String methodName;
public ModifierMethodWriter(int api, MethodVisitor mv, String methodName) {
super(api, mv);
this.methodName = methodName;
}
// This is the point we insert the code. Note that the instructions are
// added right after
// the visitCode method of the super class. This ordering is very
// important.
@Override
public void visitCode() {
// invoke methodToInject
super.visitCode();
super.visitVarInsn(ALOAD, 1);
super.visitMethodInsn(INVOKESTATIC, "Example", "methodToInject", "(Ljava/lang/Object;)V", false);
}
}
// Our class modifier class visitor. It delegate all calls to the super
// class
// Only makes sure that it returns our MethodVisitor for every method
class ModifierClassWriter extends ClassVisitor {
private int api;
public ModifierClassWriter(int api, ClassWriter cv) {
super(api, cv);
this.api = api;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (!name.equals("exampleMethod")){
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
ModifierMethodWriter mvw = new ModifierMethodWriter(api, mv, name);
return mvw;
}
}
}
class SimpleClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!className.equals("ExampleObject")) {
return classfileBuffer;
}
else {
ClassReader classReader = new ClassReader(classfileBuffer);
final ClassWriter cw = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// Wrap the ClassWriter with our custom ClassVisitor
ModifierClassWriter mcw = new ModifierClassWriter(Opcodes.ASM5, cw);
classReader.accept(mcw, 0);
byte[] byteArray = cw.toByteArray();
return byteArray;
}
}
}