java - 如何使用 ASM 读取 lambda 表达式字节码

标签 java java-bytecode-asm

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

最佳答案

编辑 01-08-2016:我添加了另一种使用 SerializedLambda 类的方法,它不需要第三方软件(即 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 类生成于通过 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 的名称,我们将看到类似 defining.class.package.DefiningClass$$ 的内容Lambda$x/y 其中 xy 是数字,现在我们可以理解为什么上面的代码不起作用了——类路径上没有这样的资源。 .

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

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

幸运的是,有一个名为 ByteBuddy 的图书馆已经创建了这样的代理,使用这个库 - 以下将起作用(如果你是一个 maven 用户,请在你的 pom 中包含字节伙伴 dep 和字节伙伴代理的依赖关系 - 请参阅下面关于限制的注释)。

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 的接口(interface) - LambdaMetafactory 类实际上生成了一个名为 writeReplace 的私有(private)方法,它提供类 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我们会发现以下方法:

public String getImplClass(): Get the name of the class containing the implementation method. Returns: the name of the class containing the implementation method

public String getImplMethodName(): Get the name of the implementation method. Returns: the name of the implementation method

这意味着您现在可以使用 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");
}

关于java - 如何使用 ASM 读取 lambda 表达式字节码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23861619/

相关文章:

java - 我可以使用 formatter-maven-plugin 格式化项目中的单个文件吗?

java - java中动态实例化对象

java - 如何在课后回滚事务(使用 TestNg 和 Spring)?

java - 方法代码太大!使用 ASM 异常

java - 在 sbt 中有效,但在原始 Java : after using `javac` to compile a . 类文件中无效,为什么 `java` 找不到它?

JAVA ASM : Why does modification cause nested exception?

java - java中分配给0的位数是0 - 为什么?

java - 如何知道在哪个项目中单击了按钮

java - 为什么 methodvistor.visitMaxs(0,0) 在 Java asm 中崩溃?

java - 如何使用ASM进行字节码转换后定义类(类文件版本0.0)