Это означает, что вы можете связать класс, который выполняет вызов метода, с классом (и методом), который получает вызов во время выполнения . Все другие инструкции байт-кода JVM для вызова метода, такие как invokevirtual , жестко связывают информацию о типе цели в вашей компиляции, то есть в файле класса. Давайте посмотрим на пример.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
Constant pool: #1 = Class #2 // com/schlimm/bytecode/examples/BytecodeExamples ... #42 = Class #43 // java/lang/String ... #65 = Methodref #42.#66 // java/lang/String.length:()I #66 = NameAndType #67:#68 // length:()I #67 = Utf8 length #68 = Utf8 ()I ...{... public void virtualMethodCall(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: ldc #44 // String Hello 2: invokevirtual #65 // Method java/lang/String.length:()I 5: pop 6: return LineNumberTable: line 31: 0 line 32: 6 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/schlimm/bytecode/examples/BytecodeExamples;} |
Приведенный выше фрагмент байт-кода показывает вызов метода invokevirtual для java.lang. String -> length () в строке 20. Он ссылается на элемент 65 в таблице пула констант, который является записью MethodRef (см. Строку 6). Элементы 42 и 66 в таблице констант пула относятся к классу и записям дескриптора метода. Как видите, тип цели и метод вызова invokevirtual полностью разрешены и встроены в байт-код. Теперь вернемся к invokedynamic !
Важно отметить, что невозможно скомпилировать код Java в байт-код, который содержит вызванную динамическую инструкцию. Ява статически типизирована . Это означает, что Java выполняет проверку типов во время компиляции. Поэтому в Java можно (и нужно!) Жестко связать всю информацию о типах получателей вызова метода в файле класса вызывающих. Вызывающая сторона знает имя типа цели вызова, как показано в нашем примере выше. Использование invokedynamic, с другой стороны, позволяет JVM разрешать именно эту информацию о типе во время выполнения. Это требуется (и требуется!) Только для динамических языков, таких как JRuby или Rhino.
Теперь предположим, что вы хотите внедрить в JVM новый язык с динамической типизацией. Я не предлагаю вам изобретать * другой * язык в JVM, но * предположим, что вы это сделаете, и * предположим, что * ваш новый язык должен быть динамически типизирован. Это означает, что на вашем новом языке связь между вызывающим и получающим вызовом метода выполняется во время выполнения. Начиная с Java 7 это возможно на уровне байт-кода с помощью инструкции invokedynamic .
Поскольку я не могу создать инструкцию invokedynamic, используя компилятор Java, я создам файл класса, который содержит invokedynamic. Как только этот файл класса будет создан, я буду запускать основной метод этого файла класса, используя обычный модуль запуска Java. Как вы можете создать файл класса без компилятора? Это возможно с помощью каркасов манипулирования байт-кодом, таких как ASM или Javassist .
В следующем фрагменте кода показан SimpleDynamicInvokerGenerator, который может сгенерировать файл класса SimpleDynamicInvoker.class, который содержит вызываемую динамическую инструкцию.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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);}public class SimpleDynamicInvokerGenerator extends AbstractDynamicInvokerGenerator { @Override protected int addMethodParameters(MethodVisitor mv) { return 0; } public static void main(String[] args) throws IOException, Exception { String dynamicInvokerClassName = "com/schlimm/bytecode/SimpleDynamicInvoker"; FileOutputStream fos = new FileOutputStream(new File("target/classes/" + dynamicInvokerClassName + ".class")); fos.write(new SimpleDynamicInvokerGenerator().dump(dynamicInvokerClassName, "com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample", "bootstrapDynamic", "()V")); } } |
Я использую ASM , универсальную среду манипулирования и анализа байт-кода Java, для создания правильного формата файла класса. В строке 30 visitInvokeDynamicInsn создает команду invokedynamic. Генерация класса, который выполняет вызываемый динамический вызов — это только половина истории. Вам также нужен код, который связывает сайт динамического вызова с реальной целью, это реальная цель invokedynamic. Вот пример.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
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)); }} |
Метод начальной загрузки в строке 9-14 выбирает фактическую цель динамического вызова. В нашем случае целью является метод sayHello (). Чтобы узнать, как метод начальной загрузки связан с инструкцией invokedynamic, нам нужно погрузиться в байт-код SimpleDynamicInvoker, который мы сгенерировали с помощью SimpleDynamicInvokerGenerator .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
E:\dev_home\repositories\git\playground\bytecode-playground\target\classes\com\schlimm\bytecode>javap -c -verbose SimpleDynamicInvoker.classClassfile /E:/dev_home/repositories/git/playground/bytecode-playground/target/classes/com/schlimm/bytecode/SimpleDynamicInvoker.class Last modified 30.01.2012; size 512 bytes MD5 checksum 401a0604146e2e95f9563e7d9f9d861bpublic class com.schlimm.bytecode.SimpleDynamicInvoker BootstrapMethods: 0: #17 invokestatic com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Utf8 com/schlimm/bytecode/SimpleDynamicInvoker #2 = Class #1 // com/schlimm/bytecode/SimpleDynamicInvoker #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 com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample #12 = Class #11 // com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample #13 = Utf8 bootstrapDynamic #14 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #15 = NameAndType #13:#14 // bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #16 = Methodref #12.#15 // com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #17 = MethodHandle #6:#16 // invokestatic com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #18 = Utf8 runCalculation #19 = NameAndType #18:#6 // runCalculation:()V #20 = InvokeDynamic #0:#19 // #0:runCalculation:()V #21 = Utf8 Code #22 = Utf8 BootstrapMethods{ public com.schlimm.bytecode.SimpleDynamicInvoker(); 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[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=0, locals=1, args_size=1 0: invokedynamic #20, 0 // InvokeDynamic #0:runCalculation:()V 5: return} |
В строке 49 вы можете увидеть команду invokedynamic. Логическое имя динамического метода — runCalculation , это вымышленное имя. Вы можете использовать любое имя, которое имеет смысл, также допускаются имена типа «+». Инструкция относится к пункту 20 в постоянной таблице пула (см. Строку 33). Это, в свою очередь, относится к индексу 0 в атрибуте BootstrapMethods (см. Строку 8). Там вы можете увидеть ссылку на метод SimpleDynamicLinkageExample.bootstrapDynamic, который связывает инструкцию invokedynamic с целью вызова.
Теперь, если вы вызываете SimpleDynamicInvoker с помощью Java Launcher, то вызывается динамический вызов.
Следующая диаграмма последовательности иллюстрирует, что происходит, когда SimpleDynamicInvoker вызывается с помощью Java- лаунчера.
Первый вызов runCalculation с использованием invokedynamic вызывает вызов метода bootstrapDynamic . Этот метод выполняет динамическую связь между вызывающим классом (SimpleDynamicInvoker) и принимающим классом ( SimpleDynamicLinkageExample ). Метод начальной загрузки возвращает MethodHandle, который нацелен на принимающий класс. Этот дескриптор метода кэшируется для повторяющихся вызовов метода runCalculation .
Это все с точки зрения invokedynamic. У меня есть более сложные примеры, опубликованные здесь в моем репозитории Git. Я надеюсь, что вам понравилось читать это — во времена нехватки!
Ура, Никлас
Ссылка:
- «Java 7: полный пример динамической работы от нашего» партнера JCG Никлас.
- http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html
- http://asm.ow2.org/
- http://java.sun.com/developer/technicalArticles/DynTypeLang/
- http://asm.ow2.org/doc/tutorial-asm-2.0.html
- http://weblogs.java.net/blog/forax/archive/2011/01/07/calling-invokedynamic-java
- http://nerds-central.blogspot.com/2011/05/performing-dynamicinvoke-from-java-step.html
