[英]How to read lambda expression bytecode using ASM
如何使用ASM從lambda表達式的主體中讀取字節碼指令?
編輯01-08-2016 :我添加了另一個使用SerializedLambda類的方法,它不需要第三方軟件(即ByteBuddy),你可以在標題為“使用SerializedLambda”的部分中閱讀它。
原答案:問題解釋+使用ByteBuddy解決它
接受的答案沒有包含有關如何在運行時通過asm實際讀取lambda字節代碼的具體信息(即,沒有javap) - 所以我想我會在此處添加此信息以供將來參考和其他人的利益。
假設以下代碼:
public static void main(String[] args) {
Supplier<Integer> s = () -> 1;
byte[] bytecode = getByteCodeOf(s.getClass()); //this is the method that we need to create.
ClassReader reader = new ClassReader(bytecode);
print(reader);
}
print(ClassReader)
代碼 - 如果沒有看到這個問題的答案。 為了通過asm讀取字節碼,你首先需要給asm(通過ClassReader
)lambda的實際字節碼 - 問題是lambda類是在運行時通過LambdaMetaFactory
類生成的,因此是普通的方法獲取字節代碼不起作用:
byte[] getByteCodeOf(Class<?> c){
//in the following - c.getResourceAsStream will return null..
try (InputStream input = c.getResourceAsStream('/' + c.getName().replace('.', '/')+ ".class")){
byte[] result = new byte[input.available()];
input.read(result);
return result;
}
}
如果我們通過c.getName()
查看類c
的名稱,我們將看到類似c.getName()
defining.class.package.DefiningClass$$Lambda$x/y
,其中x
和y
是數字,現在我們可以理解上面為什么不工作 - 類路徑上沒有這樣的資源..
雖然JVM顯然知道了類的字節碼,但遺憾的是,它沒有現成的API允許你檢索它,另一方面,JVM有一個檢測API(通過代理)允許你編寫一個類可以檢查加載(和重新加載)類的字節碼。
我們本可以編寫這樣的代理,並以某種方式告訴它我們想要接收lambda類的字節碼 - 然后代理可以請求JVM重新加載該類(不更改它) - 這將導致代理接收字節 - 重裝類的代碼並將其返回給我們。
幸運的是,我們有一個名為ByteBuddy的庫,已經使用這個庫創建了這樣的代理 - 以下內容將起作用(如果你是一個maven用戶,包括你的pom中的byte-buddy-dep和byte-buddy-agent的依賴項,另外 - 請參閱有關限制的說明)。
private static final Instrumentation instrumentation = ByteBuddyAgent.install();
byte[] getByteCodeOf(Class<?> c) throws IOException {
ClassFileLocator locator = ClassFileLocator.AgentBased.of(instrumentation, c);
TypeDescription.ForLoadedType desc = new TypeDescription.ForLoadedType(c);
ClassFileLocator.Resolution resolution = locator.locate(desc.getName());
return resolution.resolve();
}
限制: - 根據您的jvm安裝,您可能必須通過命令行安裝代理(請參閱ByteBuddyAgent文檔和Instrumentation文檔 )
新答案:使用SerializedLambda
如果您嘗試讀取的lambda實現了一個擴展Serializable
的接口 - LambdaMetafactory
類實際上生成了一個名為writeReplace
的私有方法,它提供了SerializedLambda
類的實例。 此實例可用於檢索使用LambdaMetafactory
生成的實際靜態方法。
所以,例如,有兩種方法可以使用“Serializable Lambda”:
public class Sample {
interface SerializableRunnable extends Runnable, Serializable{}
public static void main(String... args) {
SerializableRunnable oneWay = () -> System.out.println("I am a serializable lambda");
Runnable anotherWay = (Serializable & Runnable) () -> System.out.println("I am a serializable lambda too!");
}
}
在上面的例子中, oneWay
和anotherWay
都有一個生成的writeReplace
方法,可以通過以下方式使用反射檢索:
SerializedLambda getSerializedLambda(Serializable lambda) throws Exception {
final Method method = lambda.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(true);
return (SerializedLambda) method.invoke(lambda);
}
如果我們查看SerializedLambda
的javadoc,我們將找到以下方法:
public String getImplClass():獲取包含實現方法的類的名稱。 返回:包含實現方法的類的名稱
public String getImplMethodName():獲取實現方法的名稱。 返回:實現方法的名稱
這意味着您現在可以使用ASM來讀取包含lambda的類,獲取實現lambda的方法並修改/讀取它。
您甚至可以使用以下代碼獲得lambda的反射版本:
Method getReflectedMethod(Serializable lambda) throws Exception {
SerializedLambda s = getSerializedLambda(lambda);
Class containingClass = Class.forName(s.getImplClass());
String methodName = s.getImplMethodName();
for (Method m : containingClass.getDeclaredMethods()) {
if (m.getName().equals(methodName)) return m;
}
throw new NoSuchElementException("reflected method could not be found");
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.