如何使用ASMifier
一.概述
ASM这个字节码处理框架,不仅能够读字节码,而且还能修改字节码,我们经常是先写好JAVA代码,然后编译(IDE或者javac)生成class文件,最后classloader再把相应的class文件加载到内存,但是有了ASM后,我们可以直接使用ASM生成class文件的字节数组,即面向字节码写JAVA代码,这样可以让JVM动态去加载一个类,但是这个类的class文件并不存在磁盘上,也就是说这个类的class文件是在内存中构建出来的。
下面我们就来看看如何在内存中构造class文件的字节数组,并且让JVM动态加载对应的class.
二.如何使用
假如我们我们需要JVM动态加载的类的源代码如下
1 2 3 4 5 | public class ASMTest { public void hello() { System.out.println("Hello ASM"); } } |
此时这类并不再当前应用所对应的classpath中。当然我们可以自己去分析这个类的字节码,然后一步一步去构造这个类的字节码,但是这样比较麻烦,ASM提供了这样一个工具,可以把一个编译过的class文件转换一段java代码,运行这段java代码可以产生这个编译过的class文件对应的字节数组。
因此,我们先执行
1 | javac ASMTest.java
|
生成ASMTest对应的class文件。接下来我们使用ASMifier来生成可以产生字节数组的代码
1 | java -classpath asm-debug-all-4.1.jar:. org.objectweb.asm.util.ASMifier ASMTest > ASMTestDump.java |
在执行java命令的时候我们指定了classpath,多个classpath使用冒号分隔,此时一定要保证ASMTest的所在的路径被加入到classpath中,生成的ASMTestDump类如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class ASMTestDump implements Opcodes { public static byte[] dump() throws Exception { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "ASMTest", null, "java/lang/Object", null); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "hello", "()V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("Hello ASM"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } } |
接下来定义自己的classloader
1 2 3 4 5 6 | public class MyClassLoader extends ClassLoader { public Class defineClass(String clazz, byte[] bytes) { return defineClass(clazz, bytes, 0, bytes.length); } } |
使用自己的classloader加载ASMTestDump产生的字节数组,并通过反射来调用ASMTest类的Hello方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class ClassWriterTest { public static void main(String[] args) { MyClassLoader mycl = new MyClassLoader(); Class classInstance = null; try { classInstance = mycl.defineClass("ASMTest", ASMTestDump.dump()); } catch (Exception e) { e.printStackTrace(); } if (classInstance != null) { try { Object oo = classInstance.newInstance(); Method method = classInstance.getMethod("hello", null); method.invoke(oo, null); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } } |
使用ASMifier就这么简单。
三.总结
ASMifier其实也可以认为是一个编译器,把class文件转换成java代码,这里的java代码并不是反编译后的java代码,而是一段可以生成当前class字节数组的代码,可能很多人会有以为,class文件到字节数组需要这么麻烦吗?我们直接读取class文件,然后字节转换成字节数组不就搞定了么,不用这么绕一大圈的么,关键是有时候字节码并不是存在磁盘上的,需要在内存中动态构建,如果你愿意在内存中写字节码也行。