JVM方法句柄
方法句柄是一個強類型的,能夠被直接執行的引用。該引用可以指向常規的靜態方法或者實例方法,也可以指向構造器或者字段。當指向字段時,方法句柄實則指向包含字段訪問字節碼的虛構方法,語義上等價於目標字段的 getter 或者 setter 方法
**方法句柄的類型(MethodType)**是由所指向方法的參數類型以及返回類型組成的。它是用來確認方法句柄是否適配的唯一關鍵。當使用方法句柄時,我們其實並不關心方法句柄所指向方法的類名或者方法名
**方法句柄的創建是通過 MethodHandles.Lookup 類來完成的。**它提供了多個 API,既可以使用反射 API 中的 Method 來查找,也可以根據類、方法名以及方法句柄類型來查找
**當使用後者這種查找方式時(方法句柄類型),**用戶需要區分具體的調用類型,比如說對於用 invokestatic 調用的靜態方法,我們需要使用 Lookup.findStatic 方法;對於用 invokevirutal 調用的實例方法,以及用 invokeinterface 調用的接口方法,我們需要使用 findVirtual 方法;對於用 invokespecial 調用的實例方法,我們則需要使用 findSpecial 方法。
具體操作
方法類型
一個 Java 方法可以視爲由四個基本內容所構成:
-
名稱
-
簽名(包含返回類型)
-
定義它的類
-
實現方法的字節碼
方法句柄首先需要的一個構建塊就是表達方法簽名的方式,以便於查找。在 Java 7 引入的 Method Handles API 中,**這個角色是由 java.lang.invoke.MethodType 類來完成的,它使用一個不可變的實例來代表簽名。要獲取 MethodType,**我們可以使用 methodType() 工廠方法。這是一個參數可變(variadic)的方法,以 class 對象作爲參數。
第一個參數所使用的 class 對象,對應着簽名的返回類型;剩餘參數中所使用的 class 對象,對應着簽名中方法參數的類型。例如:
//toString() 的簽名
MethodType mtToString = MethodType.methodType(String.class);
// setter 方法的簽名
MethodType mtSetter = MethodType.methodType(void.class, Object.class);
// Comparator 中 compare() 方法的簽名
MethodType mtStringComparator = MethodType.methodType(int.class, String.class)
**現在可以使用 MethodType,再組合方法名稱以及定義方法的類來查找方法句柄。**要實現這一點,**我們需要調用靜態的 MethodHandles.lookup() 方法。**這樣的話,會給我們一個“查找上下文(lookup context)”,這個上下文基於當前正在執行的方法(也就是調用 lookup() 的方法)的訪問權限。
**查找上下文對象有一些以“find”開頭的方法,例如,findVirtual()、findConstructor()、findStatic() 等。這些方法將會返回實際的方法句柄,**需要注意的是,只有在創建查找上下文的方法能夠訪問(調用)被請求方法的情況下,纔會返回句柄。這與反射不同,我們沒有辦法繞過訪問控制。換句話說,方法句柄中並沒有與 setAccessible() 對應的方法。
通過MethodHandle進行方法調用一般需要以下幾步:
-
(1)創建MethodType對象,指定方法的簽名;
-
(2)在MethodHandles.Lookup中查找類型爲MethodType的MethodHandle;
-
(3)傳入方法參數並調用MethodHandle.invoke或者MethodHandle.invokeExact方法。
MethodType
可以通過MethodHandle類的type方法查看其類型,返回值是MethodType類的對象。也可以在得到MethodType對象之後,調用MethodHandle.asType(mt)方法適配得到MethodHandle對象。可以通過調用MethodType的靜態方法創建MethodType實例,有三種創建方式:
-
(1)methodType及其重載方法:需要指定返回值類型以及0到多個參數;
-
(2)genericMethodType:需要指定參數的個數,類型都爲Object;
-
(3)fromMethodDescriptorString:通過方法描述來創建。
Lookup
MethodHandle.Lookup相當於MethodHandle工廠類,通過findxxx方法可以得到相應的MethodHandle
幾個 MethodHandle 方法與字節碼的對應:
-
findStatic
對應字節碼:invokestatic
調用靜態方法 -
findSpecial
對應字節碼:invokespecial
調用實例構造方法,私有方法,父類方法 -
findVirtual
對應字節碼:invokevirtual
調用所有的虛方法 -
findVirtual
對應字節碼:invokeinterface
調用接口方法,會在運行時再確定一個實現此接口的對象
invoke
在得到MethodHandle後就可以進行方法調用了,有三種調用形式:
-
(1)invokeExact:調用此方法與直接調用底層方法一樣,需要做到參數類型精確匹配;
-
(2)invoke:參數類型鬆散匹配,通過asType自動適配;
-
(3)invokeWithArguments:直接通過方法參數來調用。其實現是先通genericMethodType方法得到MethodType,再通過MethodHandle的asType轉換後得到一個新的MethodHandle,最後通過新MethodHandle的invokeExact方法來完成調用。
官方文檔例子
public class examples {
public static void main(String[] args) throws Throwable {
Object x, y; String s; int i;
MethodType mt; MethodHandle mh;
MethodHandles.Lookup lookup = MethodHandles.lookup();
// mt is (char,char)String
mt = MethodType.methodType(String.class, char.class, char.class);
mh = lookup.findVirtual(String.class, "replace", mt);
s = (String) mh.invokeExact("daddy",'d','n');
// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
// assertEquals(s, "nanny");
System.out.println(s);
System.out.println("-----------------------------");
// weakly typed invocation (using MHs.invoke)
s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
// assertEquals(s, "savvy");
System.out.println(s);
System.out.println("-----------------------------");
// mt is (Object[])List
mt = MethodType.methodType(java.util.List.class, Object[].class);
mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
assert(mh.isVarargsCollector());
x = mh.invoke("one", "two");
// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
// assertEquals(x, java.util.Arrays.asList("one","two"));
System.out.println(x);
System.out.println("-----------------------------");
// mt is (Object,Object,Object)Object
mt = MethodType.genericMethodType(3);
mh = mh.asType(mt);
x = mh.invokeExact((Object)1, (Object)2, (Object)3);
// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
// assertEquals(x, java.util.Arrays.asList(1,2,3));
System.out.println(x);
System.out.println("-----------------------------");
// mt is ()int
mt = MethodType.methodType(int.class);
mh = lookup.findVirtual(java.util.List.class, "size", mt);
i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
// invokeExact(Ljava/util/List;)I
assert(i == 3);
mt = MethodType.methodType(void.class, String.class);
mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
mh.invokeExact(System.out, "Hello, world.");
// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
}
}
MethodHandle 與 Method 區別
-
MethodHandle 在模擬 字節碼 層次的方法調用,因而可適用於所有 JVM 語言 ;
-
Reflection 在模擬 Java 層次的方法調用,僅可適用於 Java。
-
MethodHandle 可進行 JVM 的內聯優化,Reflection 屏蔽了 JVM ,所以完全沒有相應的優化。
-
MethodHandle 從 JVM 層次支持調用,只需要包含方法必要的信息,所以說是輕量級的,而 Reflection 是 Java Api 層次的反射調用,包含了方法的簽名、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含有執行權限等的運行期信息,所以說是重量級的。
-
MethodHandle 方法調用需要考慮到 字節碼,而 Reflection 則不用考慮這些。
參考文章
https://www.infoq.cn/article/Invokedynamic-Javas-secret-weapon
https://segmentfault.com/a/1190000017208820
https://www.zhihu.com/question/40427344/answer/252825611