簡體   English   中英

如何使用ASM讀取lambda表達式字節碼

[英]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 ,其中xy是數字,現在我們可以理解上面為什么不工作 - 類路徑上沒有這樣的資源..

雖然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!");
    }
}

在上面的例子中, oneWayanotherWay都有一個生成的writeReplace方法,可以通過以下方式使用反射檢索:

SerializedLambda getSerializedLambda(Serializable lambda) throws Exception {
    final Method method = lambda.getClass().getDeclaredMethod("writeReplace");
    method.setAccessible(true);
    return (SerializedLambda) method.invoke(lambda);
}

如果我們查看SerializedLambdajavadoc,我們將找到以下方法:

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");
}

lambda編譯為具有合成名稱靜態方法 因此,要使用ASM讀取代碼,您可以對方法名稱進行反向工程...然后像任何其他方法一樣閱讀它。

但是如果你只想查看lambda的字節碼,那么使用javap會更簡單。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM