上一篇文章以Java8的Lambda表达式为切入点,讨论了invokedynamic(下文简称indy)指令。为了加深对indy指令的理解,本文来研究一下Groovy是如何利用indy指令的。
虽然Groovy从2.0开始支持indy,但并非是默认启用indy的,而是需要选择适当的jar(带indy后缀),和命令行参数(-indy)。下载Groovy2.3,为了方便起见,我们使用里面的groovy-all-2.3.0-indy.jar:
创建一个文件夹,把groovy-all-2.3.0-indy.jar提取到里面。然后再在文件夹里创建一个groovy类,名为InDyTest.groovy,内容如下:
class InDyTest { def m(x, y) { x + y } }打开命令行窗口,cd到这个文件夹下,用下面的命令编译InDyTest.groovy:
java -cp groovy-all-2.3.0-indy.jar org.codehaus.groovy.tools.FileSystemCompiler -indy InDyTest.groovy如果Java环境正确安装的话,应该能编译出InDyTest.class,然后用下面的命令反编译它:
javap -v -p InDyTest.class > InDyTest_javap.txt到此为止,你创建的文件夹下面应该包含四个文件,如下图所示:
用文本编辑器打开InDyTest_javap.txt,找到m()方法,可以看到,groovyc确实为x+y这句代码(实际上是x.plus(y))生成了一条indy指令:
找到常量池#43:
根据上一篇文章的介绍,知道它表示的是像下面这样一个动态调用:
Object invoke(Object, Object) {...}再看BootstrapMethod#0:
于是我们知道启动方法是IndyInterface.bootstrap()。
下面是IndyInterface.bootstrap()方法的代码:
/** * bootstrap method for method calls from Groovy compiled code with indy * enabled. This method gets a flags parameter which uses the following * encoding:<ul> * <li>{@value #SAFE_NAVIGATION} is the flag value for safe navigation see {@link #SAFE_NAVIGATION}<li/> * <li>{@value #THIS_CALL} is the flag value for a call on this see {@link #THIS_CALL}</li> * </ul> * @param caller - the caller * @param callType - the type of the call * @param type - the call site type * @param name - the real method name * @param flags - call flags * @return the produced CallSite * @since Groovy 2.1.0 */ public static CallSite bootstrap(Lookup caller, String callType, MethodType type, String name, int flags) { boolean safe = (flags&SAFE_NAVIGATION)!=0; boolean thisCall = (flags&THIS_CALL)!=0; boolean spreadCall = (flags&SPREAD_CALL)!=0; int callID; if (callType.equals(CALL_TYPES.METHOD.getCallSiteName())) { callID = CALL_TYPES.METHOD.ordinal(); } else if (callType.equals(CALL_TYPES.INIT.getCallSiteName())) { callID = CALL_TYPES.INIT.ordinal(); } else if (callType.equals(CALL_TYPES.GET.getCallSiteName())) { callID = CALL_TYPES.GET.ordinal(); } else if (callType.equals(CALL_TYPES.SET.getCallSiteName())) { callID = CALL_TYPES.SET.ordinal(); } else if (callType.equals(CALL_TYPES.CAST.getCallSiteName())) { callID = CALL_TYPES.CAST.ordinal(); } else { throw new GroovyBugError("Unknown call type: "+callType); } return realBootstrap(caller, name, callID, type, safe, thisCall, spreadCall); }根据class文件分析可知,当JVM调用bootstrap()方法时,传入的callType是"invoke"(==CALL_TYPES.METHOD.getCallSiteName()),name是"plus",flags是0。于是上面的方法实际上等价于:
public static CallSite bootstrap(Lookup caller, String callType, MethodType type, String name, int flags) { return realBootstrap(caller, "plus", CALL_TYPES.METHOD, type, false, false, false); }
/** * backing bootstrap method with all parameters */ private static CallSite realBootstrap(Lookup caller, String name, int callID, MethodType type, boolean safe, boolean thisCall, boolean spreadCall) { // since indy does not give us the runtime types // we produce first a dummy call site, which then changes the target to one, // that does the method selection including the the direct call to the // real method. MutableCallSite mc = new MutableCallSite(type); MethodHandle mh = makeFallBack(mc,caller.lookupClass(),name,callID,type,safe,thisCall,spreadCall); mc.setTarget(mh); return mc; }realBootstrap()方法比较复杂,我自己也没彻底搞清楚。初略的说,这个方法返回一个CallSite,这个CallSite的目标MethodHandle最终会调用selectMethod()方法,然后selectMethod()方法会根据caller等信息选出最合适的一个方法进行调用。
/** * Core method for indy method selection using runtime types. */ public static Object selectMethod(MutableCallSite callSite, Class sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws Throwable { Selector selector = Selector.getSelector(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments); selector.setCallSiteTarget(); MethodHandle call = selector.handle.asSpreader(Object[].class, arguments.length); call = call.asType(MethodType.methodType(Object.class,Object[].class)); return call.invokeExact(arguments); }
Groovy为了利用indy指令,大量使用了MethodHandle。所以,要想彻底理解indy指令,还是先彻底理解MethodHandle(以及相关的)类吧 :(
Using Groovy to play with invokedynamic