ในตอนที่ 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)