DEV Community

Pramoth Suwanpech
Pramoth Suwanpech

Posted on

Java InvokeDynamic (part 3 bytecode)

code ประกอบ

ในตอนที่ 2 part 2 เราได้เขียนโปรแกรมจำลองการทำงานของ invokedynamic ด้วย java api ไปแล้ว สำหรับในตอนนี้เราจะสร้าง class file ( .class ) ขึ้นมาให้คล้ายกับ part 2 เพื่อให้เห็นว่าใน class file หรือ bytecode นั้นมีส่วนประกอบอะไรบ้างเมื่อมีการใช้งาน invokedynamic โดยใช้ bytecode manipulation ที่ชื่อว่า ASM

ในบทความเราจะต้องปรับ MethodHandleTest2.java ให้สอดคล้องกับ bytecode ที่สร้างใหม่ เพราะเดิม public static CallSite boostrapMethod() จะไม่สามารถเรียกได้จากคลาสอื่น จึงต้องปรับ private เป็น public ทั้ง boostrapMethod() และ target()
และปรับ CallSite ที่รีเทริน method handle ของ current class ไปเป็น MethodHandleTest2.class เพราะว่า current ในบทความนี้จะเป็นคนละคลาส

  • MethodHandleTest2.java จาก part 2 ที่ถูกแก้แล้ว

public class MethodHandleTest2 {
  //...... Ignore detail

    // #1
   //เปลี่ยนเป็น public และระบุคลาสใน findStatic เป็น MethodHandleTest2
    public static CallSite boostrapMethod(MethodHandles.Lookup callerContextLookup, String name, MethodType methodType) throws NoSuchMethodException, IllegalAccessException {
        final MethodHandle methodHandle = callerContextLookup.findStatic(MethodHandleTest2.class, name, methodType);
        CallSite callSite = new ConstantCallSite(methodHandle);
        return callSite;
    }
    //เปลี่ยนเป็น public 
    //target method  ของเรา มี type description (Ljava/lang/String;)V
    public static void target(String msg) {
        System.out.println("Hello " + msg);
    }
}

  • MethodHandleTest3.java เป็นคลาสที่เอาไว้ generate InvokeDynamic.class ขั้นตอนการสร้าง comment อธิบายไว้หมดแล้ว โดยเราจะใช้ bootstrap method และ target method เดิมจาก part 2
public class MethodHandleTest3 {
    final static String JAVA_LANG_OBJECT_CLASSNAME = "java/lang/Object";
    final static String CLASS_NAME = "th/co/geniustree/indy/InvokeDynamic";

    /**
     * ใช้รันเพื่อสร้าง InvokeDynamic.class  **ไม่ต้องสนใจ**
     **/
    public static void main(String[] args) throws IOException {
        final ClassWriter cw = new ClassWriter(0);
        // สร้างคลาส public InvokeDynamic exntend Object
        cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, CLASS_NAME, null, JAVA_LANG_OBJECT_CLASSNAME, null);

        //สร้าง constructor ที่จะต้องมีทุกคลาสอยู่อยู่ ไม่ต้องสนใจตรงนี้
        createDefaultConstructor(cw);

        // สร้าง  public static void main(String[] args) ซึ่ง invokedynamic จะอยู่ในนี้ **สำคัญ**
        create_InvokeDynamic_Main_Method(cw);
        cw.visitEnd();
        // เขียน bytecode ลงที่ target/classes/th/co/geniustree/indy/InvokeDynamic.class
        writeBytecodeToFile(cw);
    }

    /**
     * สร้าง เขียนลงไฟล์  **ไม่ต้องสนใจ**
     **/
    private static void writeBytecodeToFile(ClassWriter cw) throws IOException {
        String targetPath = "target/classes/"+CLASS_NAME+".class";
        try (FileOutputStream fos = new FileOutputStream(new File(targetPath))) {
            fos.write(cw.toByteArray());
        }

    }

    /**
     * สร้าง default constructor **ไม่ต้องสนใจ**
     **/
    private static void createDefaultConstructor(ClassWriter cw) {
        MethodVisitor mv;// Create a standard void constructor
        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
    }

    /** bytecode ที่เดกี่ยวกับ invokedynamic อยู่ในนี้ **/
    private static void create_InvokeDynamic_Main_Method(ClassWriter cw) {
        MethodVisitor mv;
        // สร้าง  public static void main(String[]) สำหรับรันทดสอบ
        mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
        mv.visitCode();

        // ตัวแปร bootstrapMethodType เป็น method descripter ของ bootstrap method ที่เราต้องการเรียก
        // มีพารามิเตอร์ (MethodHandles.Lookup,String,MethodType) และรีเทิร์น CallSite
        String bootstrapMethodType = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";

        // ตัวแปร bootstrap คือ api  ของ ASM ที่จะถูกแปลงไปเป็น BootstrapMethods attribute ที่ index 0 เพราะมีตัวเดียวในคลาสนี้
        // ซึ่ง invokedynamic จะไปเรียก static method ที่ชื่อ MethodHandleTest2.boostrapMethod() ที่เราเขียนไว้ใน part 2 อีกที
        Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "th/co/geniustree/indy/MethodHandleTest2", "boostrapMethod", bootstrapMethodType, false);

        // ก่อนที่จะเรียก MethodHandleTest2.target(String) จะต้อง push ค่า "World" ลงไปใน operand stack ก่อน
        mv.visitLdcInsn("World");

        // สร้าง bytecode เพื่อเรียก method MethodHandleTest2.target()
        mv.visitInvokeDynamicInsn("target", "(Ljava/lang/String;)V", bootstrap);

        //return void
        mv.visitInsn(RETURN);

        // stack size 1 เนื่องจาก ก่อนเรียก MethodHandleTest2.target(String) จะต้อง push ค่าของตัวแปร message ลงใน operand stack ก่อนเรียกใช้งาน
        // local variable 2 คือ String[] args และ String message
        mv.visitMaxs(1, 2);
        mv.visitEnd();
    }
}

เมื่อเรารัน MethodHandleTest3 เสร็จเราจะได้คลาส th.co.geniustree.indy.InvokeDynamic

โดยเราสามารถเรียกใช้ด้วยคำสั่งด้านล่างก็จะพิมพ์ Hello World ออกมา เป็นอันว่า invokedynamic ทำงานได้ถูกต้อง

java -cp target/classes th.co.geniustree.indy.InvokeDynamic
# ผลลัพล์ "Hello World"

ถ้าเราตรวจสอบ bytecode ด้วย
javap -p -v target.classes.th.co.geniustree.indy.InvokeDynamic

เราจะเห็น InvokeDynamic และ MethodHandle เพิ่มเข้ามาใน constant pool และจะมี BootstrapMethods attribute เพิ่มเข้ามาใน bytecode อีกด้วย
ตามนี้ครับ

public class th.co.geniustree.indy.InvokeDynamic
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               th/co/geniustree/indy/InvokeDynamic
   #2 = Class              #1             // th/co/geniustree/indy/InvokeDynamic
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = NameAndType        #5:#6          // "<init>":()V
   #8 = Methodref          #4.#7          // java/lang/Object."<init>":()V
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               World
  #12 = String             #11            // World
  #13 = Utf8               th/co/geniustree/indy/MethodHandleTest2
  #14 = Class              #13            // th/co/geniustree/indy/MethodHandleTest2
  #15 = Utf8               boostrapMethod
  #16 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #17 = NameAndType        #15:#16        // boostrapMethod:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #18 = Methodref          #14.#17        // th/co/geniustree/indy/MethodHandleTest2.boostrapMethod:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #19 = MethodHandle       #6:#18         // invokestatic th/co/geniustree/indy/MethodHandleTest2.boostrapMethod:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #20 = Utf8               target
  #21 = Utf8               (Ljava/lang/String;)V
  #22 = NameAndType        #20:#21        // target:(Ljava/lang/String;)V
  #23 = InvokeDynamic      #0:#22         // #0:target:(Ljava/lang/String;)V
  #24 = Utf8               Code
  #25 = Utf8               BootstrapMethods
{
  public th.co.geniustree.indy.InvokeDynamic();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #12                 // String World
         2: invokedynamic #23,  0             // InvokeDynamic #0:target:(Ljava/lang/String;)V
         7: return
}
BootstrapMethods:
  0: #19 invokestatic th/co/geniustree/indy/MethodHandleTest2.boostrapMethod:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:

ใน part4 เราจะมาดูว่าฟีเจอร์ของภาษา Java 8 Lambda เขาอิมพรีเมนต์กันยังไง

Top comments (0)