最近在学习jacoco,看源码的时候发现插桩是用的asm插入字节码,所以准备学习下这个东东。话不多说,搞起
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
- 项目引入asm库
<properties>
<!-- Dependencies versions -->
<asm.version>9.1</asm.version>
</properties>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
- 写一个类,用asm的api 生成一个类,详情看注释
public class AsmTest {
@Test
public void test(){
ClassWriter classWriter = new ClassWriter(0);
{
/**
* 创建一个构造方法
*/
//visitMethod(修饰符、全类名、签名、父类、实现的接口)
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Z)V", null, null);
// 加载隐含的this对象,这是每个JAVA方法都有的
mv.visitVarInsn(Opcodes.ALOAD, 0);
//INVOKESPECIAL用来调用当前类的实例化方法,私有方法以及父类的方法
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
/**
* 定义类的内容
*/
// 定义对象头:版本号、修饰符、全类名、签名、父类、实现的接口
classWriter.visit(Opcodes.V1_7,Opcodes.ACC_PUBLIC,"com/zzu/asm/AccSum",null,"java/lang/Object",null);
// 添加方法;修饰符、方法名、参数(2个int类型的入参,返回值int)、签名、异常
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sum", "(II)I", null, null);
mv.visitVarInsn(Opcodes.ILOAD,1);
mv.visitVarInsn(Opcodes.ILOAD,2);
mv.visitInsn(Opcodes.IADD); //2个int类型相加
//返回int 类型
mv.visitInsn(Opcodes.IRETURN);
// 设置操作数栈的深度和局部变量的大小:2个数计算,加上this 总共3个变量
mv.visitMaxs(2, 3);
mv.visitEnd();
}
//类写完
classWriter.visitEnd();
byte[] data = classWriter.toByteArray();
writeToFile("AccSum.class",data);
}
public static void writeToFile(String className,byte[] data) {
File dataFile = new File(className);
try {
FileOutputStream outputStream = new FileOutputStream(dataFile);
outputStream.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
- 用jd-gui 查看生成的文件
暂时告一段路,发现一个问题:变量名自动生成的,int类型的是paramsInt1,boolean类型是paramBoolean, 如果想定制变量名改怎么做呢?
用到了 visitLocalVariable 创建局部变量
{
/**
*定义本地变量,可以用在定义方法里面
*/
mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sumA", "(II)I", null, null);
//Starts the visit of the method's code
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(7,l0);
mv.visitVarInsn(Opcodes.ILOAD,1);
mv.visitVarInsn(Opcodes.ILOAD,2);
mv.visitInsn(IADD);
mv.visitInsn(IRETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("numA", "I", null, l0, l1, 1);
mv.visitLocalVariable("numB", "I", null, l0, l1, 2);
mv.visitMaxs(2, 3);
mv.visitEnd();
}
用jd-gui 查看生成的文件
基础类型描述符
Java 类型 | 类型描述符 |
---|---|
boolean | Z |
char | C |
byte | B |
short | S |
int | I |
float | F |
long | J |
double | D |
Object | Ljava/lang/Object; |
int[] | [I |
Object | [[Ljava/lang/Object; |
- 方法描述符
源文件中的方法声明 | 方法描述符 |
---|---|
void m(int i, float f) | (IF)V |
int m(Object o) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int[] i) | ([I)Ljava/lang/Object; |