Java字节码插装是指在程序运行期间通过修改Java程序的字节码来达到修改程序行为和进行调试的目的。常见的字节码插装技术有Java Agent和AspectJ。
Java字节码插装的作用主要分为以下两个方面:
- 类加载时期修改类的字节码,在程序运行时对其进行增强
- 在程序运行时,通过对方法的字节码进行修改,实现将自己的代码嵌入到目标方法的中间或结尾位置
常见的应用场景有:
-
监控与诊断:通过字节码插装技术来监控Java程序的运行状态,如对内存、线程、方法执行时间等进行监控和分析,从而提高应用的健壮性和性能。例如,可以使用字节码插装实现Java监控工具,或者将统计数据插入目标方法中。
-
AOP编程:通过字节码插装技术实现面向切面编程(AOP),实现横向扩展和重用目标代码的功能。例如,可以使用AspectJ实现具有交叉功能的切面,从而减少代码重复。
下面给出两个Java字节码插装的示例:
示例一
一个简单的应用场景是:在程序运行过程中,记录方法的执行时间,以便分析哪个方法最耗费时间,并进行优化。使用字节码插装技术可以很方便的实现这一功能。
步骤
-
编写Agent
public class TimeCostAgent { public static void premain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new TimeCostTransformer()); } }
-
实现Transformer
实现一个Transformer,通过在方法的字节码中添加代码记录方法执行耗时。在返回的过程中输出耗时信息。
public class TimeCostTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.startsWith("com/example/")) {
ClassReader classReader = new ClassReader(classfileBuffer);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
TimeCostClassVisitor classVisitor = new TimeCostClassVisitor(classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
}
return classfileBuffer;
}
}
- 实现ClassVisitor
实现一个ClassVisitor,遍历类的方法,在方法字节码的头部和尾部添加代码记录时间,可以使用ASM框架实现。
public class TimeCostClassVisitor extends ClassVisitor {
private String className;
public TimeCostClassVisitor(ClassWriter classWriter) {
super(Opcodes.ASM5, classWriter);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.className = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new TimeCostMethodVisitor(mv, className, name, desc);
}
}
- 实现MethodVisitor
实现一个MethodVisitor,通过在头部和尾部添加代码记录时间。在方法返回的时候输出运行时间。
public class TimeCostMethodVisitor extends MethodVisitor {
private String className;
private String methodName;
public TimeCostMethodVisitor(MethodVisitor methodVisitor, String className, String methodName, String desc) {
super(Opcodes.ASM5, methodVisitor);
this.className = className;
this.methodName = methodName;
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(className + "." + methodName + " start at " + new Date());
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, 1);
}
@Override
public void visitInsn(int opcode) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(className + "." + methodName + " end at " + new Date());
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, 3);
mv.visitVarInsn(Opcodes.LLOAD, 3);
mv.visitVarInsn(Opcodes.LLOAD, 1);
mv.visitInsn(Opcodes.LSUB);
mv.visitVarInsn(Opcodes.LSTORE, 5);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(className + "." + methodName + " cost " );
mv.visitVarInsn(Opcodes.LLOAD, 5);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
- 实现Agentmain或者premain
在程序运行前通过Java Agent加载Agent,在加载的时候将Transformer添加到Agent中。
public class TimeCostAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new TimeCostTransformer());
}
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new TimeCostTransformer());
}
}
示例二
字节码插装的另一个应用场景是:使用字节码插装工具修改已有类的行为,达到自己的业务需求。
例如,有一个类:
public class Hello {
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
}
现在需要修改这个类的sayHello方法的行为,增加一个参数age,输出Hello之前判断age是否大于18,如果大于18则输出"Hello, " + name + "!",否则输出"You are not legal!"
步骤
-
编写Agent
public class HelloAgent { public static void premain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new HelloTransformer()); } }
-
实现Transformer
实现一个Transformer,通过修改方法字节码来修改类行为。使用ASM框架实现。
public class HelloTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("Hello")) {
ClassReader classReader = new ClassReader(classfileBuffer);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classReader.accept(new HelloClassVisitor(classWriter), ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
}
return classfileBuffer;
}
}
- 实现ClassVisitor
实现一个ClassVisitor,遍历类的方法,在方法字节码中添加代码实现自己的业务需求。可以使用ASM框架实现。
public class HelloClassVisitor extends ClassVisitor {
public HelloClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("sayHello")) {
mv = new HelloMethodVisitor(mv);
}
return mv;
}
}
- 实现MethodVisitor
实现一个MethodVisitor,在方法字节码中添加代码实现自己的业务需求。增加一个参数age,输出Hello之前判断age是否大于18。
public class HelloMethodVisitor extends MethodVisitor {
public HelloMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM5, methodVisitor);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitIntInsn(Opcodes.BIPUSH, 18);
mv.visitVarInsn(Opcodes.ILOAD, 2);
Label label = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPLE, label);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Hello, ");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn("!");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;", false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(label);
}
}
- 实现Agentmain或者premain
在程序运行前通过Java Agent加载Agent,在加载的时候将Transformer添加到Agent中。
public class HelloAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new HelloTransformer());
}
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new HelloTransformer());
}
}
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java字节码插装的作用是什么? - Python技术站