Java InvokeDynamic (part 3 bytecode)
Tags: java, invokedynamic, methodhandle
Published: 2020-04-01 16:46:44

---
title: Java InvokeDynamic (part 3 bytecode)
published: true
description: เข้าใจ InvokeDynamic ไปจนถึง Lambda
tags: java,invokedynamic,methodhandle
---

> [code ประกอบ](https://github.com/pramoth/invokedynamic)

ในตอนที่ 2 [part 2](https://dev.to/pramoth/java-invokedynamic-part-3-callsite-1d68) เราได้เขียนโปรแกรมจำลองการทำงานของ 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 ที่ถูกแก้แล้ว
```java

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](https://dev.to/pramoth/java-invokedynamic-part-3-callsite-1d68)
```java
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 ทำงานได้ถูกต้อง
```sh
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 เขาอิมพรีเมนต์กันยังไง