Java中的invokedynamic

invokedynamic字节码不能直接通过Java的编译器来直接生成的,它只有在碰到lambda的时候,会生成。

那么我们如果来测试invokedynamic字节码呢。可以通过asm-all-4.0.jar来自己生成。其实这个命名主要是为了动态语言而生的,

一般只有在写动态语言的解释器来需要用到。

可以看看下面一个例子,来了解一下invokedynamic是如何生成的使用的。

首先写一个生成invokedynamic的抽象类:

package invokedynamic;

import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

@SuppressWarnings("unused")
public abstract class AbstractDynamicInvokerGenerator implements Opcodes {

    public byte[] dump(String dynamicInvokerClassName, String dynamicLinkageClassName, String bootstrapMethodName, String targetMethodDescriptor)
            throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, dynamicInvokerClassName, 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 + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
                    MethodType.class);
            Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, dynamicLinkageClassName, bootstrapMethodName,
                    mt.toMethodDescriptorString());
            int maxStackSize = addMethodParameters(mv);
            mv.visitInvokeDynamicInsn("runCalculation", targetMethodDescriptor, bootstrap); //注意这里
            mv.visitInsn(RETURN);
            mv.visitMaxs(maxStackSize, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    protected abstract int addMethodParameters(MethodVisitor mv);

}

asm通过

visitInvokeDynamicInsn

命令来生成invokedynamic指令。

然后再实现一个通过invokedynamic来调用SimpleDynamicLinkageExample类bootstrapDynamic方法;

package invokedynamic.generator;

import invokedynamic.AbstractDynamicInvokerGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.MethodVisitor;

public class SimpleDynamicInvokerGenerator extends
        AbstractDynamicInvokerGenerator {

    @Override
    protected int addMethodParameters(MethodVisitor mv) {
        return 0;
    }

    public static void main(String[] args) throws IOException, Exception {
        String dynamicInvokerClassName = "invokedynamic/generator/SimpleDynamicInvoker";
        FileOutputStream fos = new FileOutputStream(new File("bin/"
                + dynamicInvokerClassName + ".class"));
        fos.write(new SimpleDynamicInvokerGenerator().dump(
                dynamicInvokerClassName,
                "invokedynamic/linkageclasses/SimpleDynamicLinkageExample",
                "bootstrapDynamic", "()V"));
    }

}

然后实现SimpleDynamicLinkageExample类:

package invokedynamic.linkageclasses;

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

@SuppressWarnings({ "unused", "rawtypes" })
public class SimpleDynamicLinkageExample {

    private static MethodHandle sayHello;

    private static void sayHello() {
        System.out.println("There we go!");
    }

    public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        Class thisClass = lookup.lookupClass(); // (who am I?)
        sayHello = lookup.findStatic(thisClass, "sayHello", MethodType.methodType(void.class));
        return new ConstantCallSite(sayHello.asType(type));
    }

}

这是需要的类就写完了,需要注意的是,你需要先在Eclipse中运行SimpleDynamicInvokerGenerator,让它生成invokedynamic/generator/SimpleDynamicInvoker.class文件;

这个类是我们的入口类,还需要注意的是,我是在Eclipse中编译这些文件的,如果你的bin目录指定到别的地方,你需要修改SimpleDynamicInvokerGenerator类中的路径;

因为生成的SimpleDynamicInvoker类对Eclipse不可见,所以我们需要在命令行中来执行它;

先cd到你的工程目录下,执行

C:\Users\tmp\workspace_java\SimpleDyn\bin>java -classpath
“C:\Users\tmp\workspace_java\SimpleDyn\bin;”
invokedynamic.generator.SimpleDynamicInvoker
There we go!

那么只是怎么完成的呢:

看看SimpleDynamicInvokerGenerator的内容就知道了:

//  (version 1.7 : 51.0, super bit)
public class invokedynamic.generator.SimpleDynamicInvoker {

  // Method descriptor #6 ()V
  // Stack: 1, Locals: 1
  public SimpleDynamicInvoker();
    0  aload_0 [this]
    1  invokespecial java.lang.Object() [8]
    4  return


  // Method descriptor #10 ([Ljava/lang/String;)V
  // Stack: 0, Locals: 1
  public static void main(java.lang.String[] arg0);
    0  invokedynamic 0 runCalculation() : void [20]
    5  return

Bootstrap methods:
  0 : # 17 invokestatic invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:

}

在class中JVM通过invokedynamic来调用runCalculation方法,这个方法是我们通过asm来写入的,可以是任意的名字;

然后我们用通过Bootstrap方法将runCalculation和

SimpleDynamicLinkageExample

bootstrapDynamic

方法关联起来;

SimpleDynamicLinkageExample

中我们有通过MethodHandle找到其中的sayHello方法,然后调用它,这样就完成了invokedynamic命令和实际的函数关联了起来。


http://niklasschlimm.blogspot.com/2012/02/java-7-complete-invokedynamic-
example.html