如何在Javassist生成方法中调用带@PolymorphicSignature的MethodHandle.invokeExact?
解决Javassist中调用MethodHandle.invokeExact的编译错误
这问题我之前在动态生成代码时也踩过坑,核心原因就是MethodHandle.invokeExact带的**@PolymorphicSignature**注解——这个注解是给JVM看的,它告诉虚拟机这个方法没有固定的编译时签名,参数和返回值完全由调用时的方法句柄类型决定。但Javassist的源码编译器是静态类型检查的,它会傻乎乎地去MethodHandle类里找一个参数是int(或者你传的其他类型)的invokeExact方法,自然找不到,就报编译错误了。
下面给你两种可靠的解决办法:
方法1:直接生成字节码调用(推荐,无性能开销)
既然Javassist的源码编译器搞不定这种动态签名,我们就绕开它,直接操作字节码生成调用指令。步骤很清晰:
- 先拿到你生成的目标方法的字节码迭代器:
// 假设你已经生成了要修改的CtMethod CtMethod targetMethod = ...; MethodInfo methodInfo = targetMethod.getMethodInfo(); CodeAttribute codeAttr = methodInfo.getCodeAttribute(); CodeIterator codeIter = codeAttr.iterator(); ConstPool constPool = methodInfo.getConstPool();
- 插入调用
invokeExact的字节码指令。这里需要注意:你必须提前知道MethodHandle的实际类型(可以通过MethodHandle.type()获取MethodType,再转成字节码签名)。比如,假设你的MethodHandle存在局部变量索引1的位置,要传入的int参数在索引2,返回值是void,签名就是(I)V:
// 加载MethodHandle对象到操作栈 codeIter.insertByte(Opcode.ALOAD_1); // 加载int参数到操作栈 codeIter.insertByte(Opcode.ILOAD_2); // 生成invokevirtual指令调用invokeExact int methodRef = constPool.addMethodref( "java.lang.invoke.MethodHandle", "invokeExact", "(I)V" // 替换成你的MethodHandle对应的字节码签名 ); codeIter.insertByte(Opcode.INVOKEVIRTUAL); codeIter.insertIndex(methodRef);
- 最后调整方法的栈大小和局部变量大小(或者让Javassist自动计算):
// 根据你的实际变量数量调整,这里只是示例 targetMethod.setMaxLocals(3); targetMethod.setMaxStack(2);
方法2:生成辅助方法间接调用(更直观,有轻微性能开销)
如果你觉得直接操作字节码太麻烦,可以生成一个辅助方法来封装invokeExact的调用,然后在目标方法里调用这个辅助方法。不过辅助方法也得用Javassist生成(静态编译的辅助方法会同样报错):
- 生成辅助方法:
CtClass helperClass = ClassPool.getDefault().makeClass("com.yourpackage.MethodHandleHelper"); // 辅助方法参数:MethodHandle + 可变参数,返回Object(根据你的需求调整) CtMethod helperMethod = CtNewMethod.make( "public static Object invokeExactHelper(java.lang.invoke.MethodHandle handle, Object... args) throws Throwable {", "}", helperClass ); // 给辅助方法插入字节码调用invokeExact,步骤和方法1类似,这里可以用MethodType来动态生成签名 // (省略字节码插入步骤,和方法1的逻辑一致,只是签名要适配可变参数或者你实际的参数类型) helperClass.addMethod(helperMethod); helperClass.writeFile(); // 或者加载到JVM
- 在你的目标方法中调用这个辅助方法:
// 用Javassist的源码语法调用辅助方法 targetMethod.setBody("{ return com.yourpackage.MethodHandleHelper.invokeExactHelper($1, $2); }");
关键注意事项
- 调用
invokeExact时,签名必须和MethodHandle的类型完全匹配(包括返回值、参数数量和类型),否则运行时会抛出WrongMethodTypeException; - 可以通过
methodHandle.type().toMethodDescriptorString()直接获取正确的字节码签名,不用手动拼; - 尽量使用新版本的Javassist(比如3.28+),旧版本对MethodHandle的支持可能有bug。
内容的提问来源于stack exchange,提问作者Jire
相关产品推荐
相关产品推荐

