在这种情况下没有什么可做的。您唯一需要注意的是,它有一个隐含的第一个参数,即接收者,您必须将其作为显式的第一个参数invokevirtual
插入到指令的描述符中:invokedynamic
public class ConvertToInvokeDynamic extends MethodVisitor {
public static byte[] convertInvokeVirtual(
InputStream in, String linkerClass, String linkerMethod) throws IOException {
ClassReader cr = new ClassReader(in);
ClassWriter cw = new ClassWriter(cr, 0);
cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
return new ConvertToInvokeDynamic(
super.visitMethod(access, name, desc, signature, exceptions),
linkerClass, linkerMethod);
}
}, 0);
return cw.toByteArray();
}
private final Handle bsm;
public ConvertToInvokeDynamic(
MethodVisitor target, String linkerClass, String linkerMethod) {
super(Opcodes.ASM5, target);
bsm = new Handle(Opcodes.H_INVOKESTATIC, linkerClass, linkerMethod,
"(Ljava/lang/invoke/MethodHandles$Lookup;"
+ "Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;");
}
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String desc, boolean itf) {
if(opcode == Opcodes.INVOKEVIRTUAL) {
desc = '('+(owner.charAt(0)!='['? 'L'+owner+';': owner)+desc.substring(1);
super.visitInvokeDynamicInsn(name, desc, bsm);
}
else super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
只要这是唯一的更改,堆栈状态将与原始代码中的相同,因此,我们不需要重新计算堆栈帧或最大变量/操作数堆栈大小。
该代码假定原始的类版本足够高以支持该invokedynamic
指令。否则,转换将变得不平凡,因为我们不仅可能需要计算堆栈映射,还可能会遇到旧类文件中的现在禁止jsr
和ret
指令。
提供重新建立原始invokevirtual
行为的引导方法也是直截了当的。现在,最大(不是很大)的障碍是我们现在必须提取第一个显式参数类型并将其转换回接收器类型:
public class LinkLikeInvokeVirtual {
public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType type){
Class<?> receiver = type.parameterType(0);
type = type.dropParameterTypes(0, 1);
System.out.println("linking to "+name+type+" in "+receiver);
MethodHandle target;
try {
target = l.findVirtual(receiver, name, type);
} catch(NoSuchMethodException|IllegalAccessException ex) {
throw new BootstrapMethodError(ex);
}
return new ConstantCallSite(target);
}
}
现在,我们可以将这两个类组合在一个简单的测试用例中:
public class Test {
public static void main(String[] args) throws IOException,ReflectiveOperationException{
byte[] code;
try(InputStream is = Test.class.getResourceAsStream("Test.class")) {
code = ConvertToInvokeDynamic.convertInvokeVirtual(is,
LinkLikeInvokeVirtual.class.getName(), "bootstrap");
}
Class<?> transformed = new ClassLoader() {
Class<?> get() {return defineClass("Test", code, 0, code.length); }
}.get();
transformed.getMethod("example").invoke(null);
}
public static void example() {
System.out.println(Runtime.getRuntime().freeMemory()+" bytes free");
}
}
其转化example()
产生
linking to freeMemory()long in class java.lang.Runtime
linking to append(long)StringBuilder in class java.lang.StringBuilder
linking to append(String)StringBuilder in class java.lang.StringBuilder
linking to toString()String in class java.lang.StringBuilder
linking to println(String)void in class java.io.PrintStream
131449472 bytes free
在第一次执行时(因为链接的调用站点保持链接,所以我们不会在下一次调用时看到引导方法的输出)。
这些StringBuilder
方法是在 Java 9 之前编译的字符串连接的产物,因此从 Java 9 开始,它只会打印
linking to freeMemory()long in class java.lang.Runtime
linking to println(String)void in class java.io.PrintStream
131449472 bytes free
(当然,数字会有所不同)
如果您想根据实际接收者执行替代动态调度,您可以替换LinkLikeInvokeVirtual
为以下内容:
public class LinkWithDynamicDispatch {
static final MethodHandle DISPATCHER;
static {
try {
DISPATCHER = MethodHandles.lookup().findStatic(LinkWithDynamicDispatch.class, "simpleDispatcher",
MethodType.methodType(MethodHandle.class, MethodHandle.class, String.class, Object.class));
} catch(NoSuchMethodException|IllegalAccessException ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType type){
MethodHandle target;
try {
target = l.findVirtual(type.parameterType(0), name, type.dropParameterTypes(0, 1));
} catch(NoSuchMethodException|IllegalAccessException ex) {
throw new BootstrapMethodError(ex);
}
MethodHandle d = MethodHandles.insertArguments(DISPATCHER, 0, target, name);
target = MethodHandles.foldArguments(MethodHandles.exactInvoker(type),
d.asType(d.type().changeParameterType(0, type.parameterType(0))));
return new ConstantCallSite(target);
}
public static MethodHandle simpleDispatcher(
MethodHandle invokeVirtualTarget, String methodName, Object rec) {
System.out.println("simpleDispatcher(): invoke "+methodName+" on "
+ "declared receiver type "+invokeVirtualTarget.type().parameterType(0)+", "
+ "actual receiver "+(rec==null? "null": "("+rec.getClass().getName()+"): "+rec));
return invokeVirtualTarget;
}
}
这将执行invokevirtual
基于静态类型的查找,然后链接到simpleDispatcher
将接收实际接收器实例另外到已解析目标的方法。然后它可以根据实际接收者返回目标句柄或不同的句柄。