Java暂不支持直接生成invokedynamic指令,需要借助asm工具。
1.invokedynamic指令简介
invokedynamic #8, 0 // InvokeDynamic #0:approach:(Ljava/lang/String;)Lcom/enjoy/learn/core/lambdastream/test/test3/Strategy;
invokedynamic指令后面的信息是CONSTANT_InvokeDynamic_info
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
#8 = InvokeDynamic #0:#64 // #0:approach:(Ljava/lang/String;)Lcom/enjoy/learn/core/lambdastream/test/test3/Strategy;
其中#64是方法名字和类型:
#64 = NameAndType #83:#89 // approach:(Ljava/lang/String;)Lcom/enjoy/learn/core/lambdastream/test/test3/Strategy;
另外#0表示bootstrap_method:
BootstrapMethods:
0: #61 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#62 (Ljava/lang/String;)Ljava/lang/String;
#63 REF_invokeStatic com/enjoy/learn/core/lambdastream/test/test3/Strategize.lambda$test$0:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#62 (Ljava/lang/String;)Ljava/lang/String;
invokedynamic将CallSite抽象成一个Java类,并且将由JVM控制的方法调用和方法链接暴露给应用程序。
invokedynamic会捆绑一个CallSite,调用该CallSite所链接的MethodHandle。
在第一次执行invokedynamic时,JVM会调用其对应的Boostrap方法,来生成CallSite,并且进行绑定。之后,JVM会直接调用绑定的CallSite所链接的MethodHandle。
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
该静态方法接收三个固定参数:
1)一个Lookup实例
2)目标方法名称字符串
3)MethodHandle类型MethodType(目标类型,根据实际参数生成的类型)
2.实验目的
生成一个方法,其通过invokedynamic指令调用任意类型对象的同一个方法(也就是上面的NameAndType)。
也即:
1)invokedynamic指定方法名和方法类型
2)传入对象,只要该对象具有该方法,即可进行调用
3.借用ASM添加invokedymic指令
需要在startRace中添加invokedynamic指令:
import java.lang.invoke.*;
class Horse {
public void race() {
System.out.println("Horse.race()");
}
}
class Deer {
public void race() {
System.out.println("Deer.race()");
}
}
// javac Circuit.java
// java Circuit
public class Circuit {
public static void startRace(Object obj) {
// aload obj
// invokedynamic race()
}
public static void main(String[] args) {
startRace(new Horse());
// startRace(new Deer());
}
public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType callSiteType) throws Throwable {
MethodHandle mh = l.findVirtual(Horse.class, name, MethodType.methodType(void.class));
return new ConstantCallSite(mh.asType(callSiteType));
}
}
如下是修改Circuit.class的类ASMHelper:
import java.io.IOException;
import java.lang.invoke.*;
import java.nio.file.*;
import org.objectweb.asm.*;
public class ASMHelper implements Opcodes {
private static class MyMethodVisitor extends MethodVisitor {
private static final String BOOTSTRAP_CLASS_NAME = Circuit.class.getName().replace('.', '/');
private static final String BOOTSTRAP_METHOD_NAME = "bootstrap";
private static final String BOOTSTRAP_METHOD_DESC = MethodType
.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class)
.toMethodDescriptorString();
private static final String TARGET_METHOD_NAME = "race";
private static final String TARGET_METHOD_DESC = "(Ljava/lang/Object;)V";
public final MethodVisitor mv;
public MyMethodVisitor(int api, MethodVisitor mv) {
super(api);
this.mv = mv;
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
Handle h = new Handle(H_INVOKESTATIC, BOOTSTRAP_CLASS_NAME, BOOTSTRAP_METHOD_NAME, BOOTSTRAP_METHOD_DESC, false);
mv.visitInvokeDynamicInsn(TARGET_METHOD_NAME, TARGET_METHOD_DESC, h);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
}
public static void main(String[] args) throws IOException {
ClassReader cr = new ClassReader("Circuit");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new ClassVisitor(ASM6, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("startRace".equals(name)) {
return new MyMethodVisitor(ASM6, visitor);
}
return visitor;
}
};
cr.accept(cv, ClassReader.SKIP_FRAMES);
Files.write(Paths.get("Circuit.class"), cw.toByteArray());
}
}
获取两个jar包:
asm-6.0_BETA.jar
org.objectweb.asm-5.0.1.v201404251740.jar
执行操作:
javac Circuit.java
javac -cp ./asm-6.0_BETA.jar:. ASMHelper.java
java -cp ./asm-6.0_BETA.jar:. ASMHelper
java Circuit
结果:
Horse.race()
ASMHelper.java的作用:拼接出invokedynamic的bootstrap和NameAndType
1)增加了aload和invokedynamic指令
2)增加了invokedynamic指令需要的BootstrapMethods属性
3)以及相关的常量池中的项(#75 = InvokeDynamic等)
#75 = InvokeDynamic #0:#74 // #0:race:(Ljava/lang/Object;)V
public static void startRace(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #75, 0 // InvokeDynamic #0:race:(Ljava/lang/Object;)V
6: return
BootstrapMethods:
0: #72 invokestatic Circuit.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
SourceFile: "Circuit.java"
InnerClasses:
public static final #28= #27 of #53; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
但该方法有个缺陷:
MethodHandle mh = l.findVirtual(Horse.class, name, MethodType.methodType(void.class));
mh绑死在Horse.class,若main()中调用startRace(new Deer())会出错。
4.支持调用任意类的race方法
import java.lang.invoke.*;
public class MonomorphicInlineCache {
private final MethodHandles.Lookup lookup;
private final String name;
public MonomorphicInlineCache(MethodHandles.Lookup lookup, String name) {
this.lookup = lookup;
this.name = name;
}
private Class<?> cachedClass = null;
private MethodHandle mh = null;
public void invoke(Object receiver) throws Throwable {
if (cachedClass != receiver.getClass()) {
cachedClass = receiver.getClass();
mh = lookup.findVirtual(cachedClass, name, MethodType.methodType(void.class));
}
mh.invoke(receiver);
}
import java.lang.invoke.*;
class Horse {
public void race() {
System.out.println("Horse.race()");
}
}
class Deer {
public void race() {
System.out.println("Deer.race()");
}
}
// javac Circuit.java
// java Circuit
public class Circuit {
public static void startRace(Object obj) {
// aload obj
// invokedynamic race()
}
public static void main(String[] args) {
startRace(new Horse());
startRace(new Deer());
}
public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType callSiteType) throws Throwable {
MonomorphicInlineCache ic = new MonomorphicInlineCache(l, name);
MethodHandle mh = l.findVirtual(MonomorphicInlineCache.class, "invoke", MethodType.methodType(void.class, Object.class));
return new ConstantCallSite(mh.bindTo(ic));
}
}
这里将启动方法绑定到MonomorphicInlineCache的invoke方法,一般情况下,invoke会根据对象实例的类型生成MethodHandle,然后进行调用。如果之前缓存了,则直接使用缓存的MethodHandle进行调用。因此可以实现任意类调用。