最近想捣鼓一下ASM,于是乎自己想出这么个事情来干,纯粹为了解闷。
先说说最终我们要实现的目标,反编译ASM构建好的字节码,能够出现如下图所示的效果:
public void lambda(){
IntStream.range(0,10).forEach(index->System.out.println("this is "+index));
}
这是一个非常简单的lambda表达式的方法,话不多说,我们开始编码。
public byte[] writeLambda(){
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//随便给类取个名字
cw.visit(V1_8,ACC_PUBLIC,"demo/HelloLambda",null,"java/lang/Object",null);
//无参构造
init(cw);
//创建主要方法
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,"writeLambda","()V",null,null);
mv.visitCode();
mv.visitInsn(ICONST_0);
mv.visitIntInsn(BIPUSH,10);
mv.visitMethodInsn(INVOKESTATIC,"java/util/stream/IntStream","range","(II)Ljava/util/stream/IntStream",false);
mv.visitMethodInsn(INVOKEINTERFACE,"java/util/stream/IntStream","forEach","(Ljava/util/function/IntConsumer;)V",true);
mv.visitInsn(RETURN);
mv.visitEnd();
return cw.toByteArray();
}
先写一个大概的框架,缺少的部分也非常明显。动态调用指令和匿名内部类作为方法调用点承载等部分还没有构建,所以这样直接编译出来的类肯定是不行的,如下便是idea直接反编译出来的代码:
public class HelloLambda {
public HelloLambda() {
}
public void writeLambda() {
// $FF: Couldn't be decompiled
}
}
明显是错的,那么接下来我们把重要的那部分补充完整。
总所周知,lambda表达式属于java语言层面的东西,而asm所操作的class文件,也就是javac对java文件编译后产生的字节码文件(此时已经不存在lambda表达式了),它属于jvm层面的东西,在jvm视角来看没有什么lambda表达式,它看到的只有invokeDynamic指令,CallSite方法调用点以及一个匿名内部类等这些东西罢了。
先仔细观察一下字节码:
public void lambda();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: iconst_0
1: bipush 10
3: invokestatic #5 // InterfaceMethod java/util/stream/IntStream.range:(II)Ljava/util/stream/IntStream;
6: invokedynamic #6, 0 // InvokeDynamic #0:accept:()Ljava/util/function/IntConsumer;
11: invokeinterface #7, 2 // InterfaceMethod java/util/stream/IntStream.forEach:(Ljava/util/function/IntConsumer;)V
16: return
LineNumberTable:
line 13: 0
line 14: 16
LocalVariableTable:
Start Length Slot Name Signature
0 17 0 this Lorg/example/asm/bbq;
private static void lambda$lambda$0(int);
descriptor: (I)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #8 // Field java/lang/System.out:Ljava/io