文章目录

ASM字节码操作框架,是Java中用于字节码操作的很好用的一个库,是cglib等很多著名框架的基础,我们常用的Spring,Hibernate等都用到了cglib。ASM存在的基础是,JVM虚拟机未对Java的字节码(.class)文件做“只能是文件”的限制,只要是合法的字节码文件格式,可以存在与网络上,内存中,都可以予以加载并运行。

本文使用ASM框架手动生成一段已有的Java代码,并予以加载和运行。ASM框架的代码可以去这里下载,用户手册可以去这里下载,官网是这里,官网上有一些文章和教程,可以用作入门。

生成字节码的源代码是这样,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package my;
import java.io.PrintStream;
public class Example
{
public static void main(String[] paramArrayOfString)
{
int j = 6;
int k = 7;
int i = (j + k) * 3;
System.out.println(i);
}
}

使用ASM框架,生成上述代码的字节码的代码如下,

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package problem1;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.MethodVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
/**
* 通过asm生成类的字节码
*
* @author Administrator
*
* Decompiled Code:
package my;
import java.io.PrintStream;
public class Example
{
public static void main(String[] paramArrayOfString)
{
int j = 6;
int k = 7;
int i = (j + k) * 3;
System.out.println(i);
}
}
*/
public class Problem1 {
public static void main(String[] args) throws IOException,
ClassNotFoundException, IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
| ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "my/Example", null,
"java/lang/Object", null);
// add constructor
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V",
null, null);
mw.visitVarInsn(Opcodes.ALOAD, 0); // this 入栈
mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
"()V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
// add main method
mw = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main",
"([Ljava/lang/String;)V", null, null);
// add 2 local int, a and b
mw.visitIntInsn(Opcodes.BIPUSH, 6);
mw.visitVarInsn(Opcodes.ISTORE, 3);
mw.visitIntInsn(Opcodes.BIPUSH, 7);
mw.visitVarInsn(Opcodes.ISTORE, 4);
// do calculation
mw.visitVarInsn(Opcodes.ILOAD, 3);
mw.visitVarInsn(Opcodes.ILOAD, 4);
mw.visitInsn(Opcodes.IADD);
mw.visitIntInsn(Opcodes.BIPUSH, 3);
mw.visitInsn(Opcodes.IMUL);
mw.visitVarInsn(Opcodes.ISTORE, 2);
// call println
mw.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mw.visitVarInsn(Opcodes.ILOAD, 2);
mw.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(I)V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
// store and run code
final byte[] code = cw.toByteArray();
Class exampleClass = new ClassLoader() {
@SuppressWarnings("unchecked")
protected Class findClass(String name)
throws ClassNotFoundException {
return defineClass(name, code, 0, code.length);
}
}.loadClass("my.Example");
// OutputStream out = new FileOutputStream("d:/Example.class");
// out.write(code);
// out.close();
Method method = exampleClass.getMethod("main",
new Class[] { String[].class });
method.invoke(null, new Object[] { null });
}
}

以上代码做如下解释, 1. 首先使用ClassWriter创建一个写入class字节码的对象,用cw.visit创建一个类my.Example。 2. 然后用cw.visitMethod获取到类方法的MethodVisitor对象,使用该对象新增类中的方法。 3. 先新增一个无参的构造方法,对应上述代码“add constructor”一段,方法的描述符是()V,表示无参数,返回是void,这个与Java字节码文件规范保持一致。 4. 然后类似的构造main方法,在main方法中,先使用mw.visitVarInsn创建两个临时变量,并用mw.visitIntInsn赋初始值6和7。 5. 接下来,用mw.visitInsn完成加法和乘法,并将值赋给一个新的临时变量。 6. 在main方法的结尾,调用mw.visitFieldInsn获取静态的PrintStream对象,调用mw.visitMethodInsn来完成println方法的调用,并结束方法mw.visitEnd。 7. 以上步骤已经完成了字节码的生成,调用cw.toByteArray将其保存到内存中的一个byte[]对象里,然后调用ClassLoader对象的loadClass方法,加载这个类,注意,这里要重写findClass,在其中指明使用byte[]对象里的内容作为字节码来加载。 8. 如果需要将字节码输出可以使用FileOutputStreamwrite方法,这里已经注释掉,这段代码对调试很有用,可以用class文件反编译工具来查看是否是想要的代码。 9. 最后,使用Java的反射机制,分别调用getMethodinvoke来调用之前加载的class的main方法,输出39

注意,局部变量并不可以指定名称,只可以指定在操作数栈中的位置,因此变量名i,j,k并未出现在以上代码中。可以看到,基本上,ASM是对Java字节码指令的一个比较好用的封装,与写汇编代码有以一些相似之处。如果需要生成的代码有static域,例如这样,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package my;
import java.io.PrintStream;
public class Example
{
public static int a = 6;
public static int b = 7;
public static void main(String[] paramArrayOfString)
{
int i = (a + b) * 3;
System.out.println(i);
}
}

同样可以使用ASM生成这段字节码,只是局部代码需要做一些改动,修改后的代码如下,

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package problem1;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.MethodVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
/**
* 通过asm生成类的字节码
*
* @author Administrator
*
* Decompiled Code:
package my;
import java.io.PrintStream;
public class Example
{
public static int a = 6;
public static int b = 7;
public static void main(String[] paramArrayOfString)
{
int i = (a + b) * 3;
System.out.println(i);
}
}
*/
public class GeneratorClass {
public static void main(String[] args) throws IOException,
ClassNotFoundException, IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
| ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "my/Example", null,
"java/lang/Object", null);
// add constructor
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V",
null, null);
mw.visitVarInsn(Opcodes.ALOAD, 0); // this 入栈
mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
"()V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
// add 2 static int, a and b
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "a", "I",
null, new Integer(6)).visitEnd();
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "b", "I",
null, new Integer(7)).visitEnd();
// add main method
mw = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main",
"([Ljava/lang/String;)V", null, null);
// read static variables
mw.visitFieldInsn(Opcodes.GETSTATIC, "my/Example", "a", "I");
mw.visitFieldInsn(Opcodes.GETSTATIC, "my/Example", "b", "I");
// do calculation
mw.visitInsn(Opcodes.IADD);
mw.visitInsn(Opcodes.ICONST_3);
mw.visitInsn(Opcodes.IMUL);
mw.visitVarInsn(Opcodes.ISTORE, 2);
// call println
mw.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mw.visitVarInsn(Opcodes.ILOAD, 2);
mw.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(I)V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
// store and run code
final byte[] code = cw.toByteArray();
Class exampleClass = new ClassLoader() {
@SuppressWarnings("unchecked")
protected Class findClass(String name)
throws ClassNotFoundException {
return defineClass(name, code, 0, code.length);
}
}.loadClass("my.Example");
// OutputStream out = new FileOutputStream("d:/Example.class");
// out.write(code);
// out.close();
Method method = exampleClass.getMethod("main",
new Class[] { String[].class });
method.invoke(null, new Object[] { null });
}
}

多数的代码和之前的有重复,不同之处如下, 1. 使用cw.visitField,新增静态对象a和b。 2. 使用mw.visitFieldInsn将静态对象压入操作数栈。

以上是使用ASM框架来操作Java字节码的例子,本文参考了以下文章,12345

文章目录

欢迎来到Valleylord的博客!

本博的文章尽量原创。