【问题标题】:How to read lambda expression bytecode using ASM如何使用 ASM 读取 lambda 表达式字节码
【发布时间】:2014-05-26 01:34:20
【问题描述】:

如何使用 ASM 从 lambda 表达式的主体中读取字节码指令?

【问题讨论】:

    标签: java java-bytecode-asm


    【解决方案1】:

    EDIT 01-08-2016:我使用 SerializedLambda 类添加了另一个方法,它不需要 3 方软件(即 ByteBuddy),您可以在标题为: "使用 SerializedLambda" 如下。

    原答案:问题说明 + 使用 ByteBuddy 解决问题

    接受的答案不包含有关如何在运行时通过 asm(即,没有 javap)实际读取 lambda 字节码的具体信息 - 所以我想我会在此处添加此信息以供将来参考和他人的利益.

    假设以下代码:

    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) 代码 - 如果没有,请查看 this question 的答案。

    为了通过 asm 读取字节码,您首先需要向 asm(通过 ClassReader)提供 lambda 的实际字节码 - 问题是 lambda 类是在运行时通过 @987654329 生成的@class,因此获取字节码的正常方法不起作用:

    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 类的名称,我们会看到类似defining.class.package.DefiningClass$$Lambda$x/y 的内容,其中xy 是数字,现在我们可以理解为什么上述方法不起作用了 - 那里类路径上没有这样的资源..

    虽然 JVM 显然知道类的字节码,但遗憾的是,它没有现成的 API 可以让您检索它,另一方面,JVM 有一个检测 API(通过代理)允许您编写一个可以检查加载(和重新加载)类的字节码的类。

    我们可以编写这样的代理,并以某种方式与它通信,我们希望接收 lambda 类的字节码 - 然后代理可能会请求 JVM 重新加载该类(不更改它) - 这将导致代理接收重载类的字节码并将其返回给我们。

    对我们来说幸运的是,有一个名为 ByteBuddy 的库已经使用这个库创建了这样的代理 - 以下内容将起作用(如果您是 maven 用户,请同时包含 byte-buddy-dep 和 byte-buddy- 的依赖项)您的 pom 中的代理,也 - 请参阅下面有关限制的注释)。

    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 documentationInstrumentation documentation

    新答案:使用 SerializedLambda

    如果您尝试读取的 lambda 实现了扩展 Serializable 的接口 - LambdaMetafactory 类实际上会生成一个名为 writeReplace 的私有方法,它提供类 SerializedLambda 的一个实例。此实例可用于检索使用LambdaMetafactory 生成的实际静态方法。

    因此,例如,这里有 2 种方法来拥有“可序列化 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);
    }
    

    如果我们查看javadoc of SerializedLambda,我们会发现以下方法:

    公共字符串 getImplClass(): 获取包含实现方法的类的名称。 回报: 包含实现方法的类名

    公共字符串 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");
    }
    

    【讨论】:

    • 这是一个极好的答案,尤其是 SerializedLambda 部分对我非常有帮助。你应该把它拉到答案的顶部。
    【解决方案2】:

    lambda 编译为static method with a synthetic name。因此,要使用 ASM 读取代码,您需要对方法名称进行逆向工程……然后像任何其他方法一样读取它。

    但如果你只是想查看 lambda 的字节码,使用javap 会更简单。

    【讨论】:

    • 感谢您的回复
    猜你喜欢
    • 1970-01-01
    • 2014-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多