上一篇文章以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
到此爲止,你創建的文件夾下面應該包含四個文件,如下圖所示:
觀察class文件
用文本編輯器打開InDyTest_javap.txt,找到m()方法,可以看到,groovyc確實爲x+y這句代碼(實際上是x.plus(y))生成了一條indy指令:
找到常量池#43:
根據上一篇文章的介紹,知道它表示的是像下面這樣一個動態調用:
Object invoke(Object, Object) {...}
再看BootstrapMethod#0:
於是我們知道啓動方法是IndyInterface.bootstrap()。
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);
}
realBootstrap()方法
/**
* 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