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

Jos*_*one 7 java java-bytecode-asm

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

ben*_*nyl 12

编辑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); 
}
Run Code Online (Sandbox Code Playgroud)
  • 我假设你已经有了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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们看一下在类的名称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();
}
Run Code Online (Sandbox Code Playgroud)

限制: - 根据您的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!");
    }
}
Run Code Online (Sandbox Code Playgroud)

在上述实例中都oneWayanotherWay将有一个产生writeReplace其可以使用以下面的方式反射检索方法:

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

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

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");
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个极好的答案,尤其是“SerializedLambda”部分对我“非常”有帮助。您应该将其拉到答案的顶部。 (2认同)

Ste*_*n C 3

lambda 编译为具有合成名称的静态方法。因此,要使用 ASM 读取代码,您需要对方法名称进行逆向工程……然后像任何其他方法一样读取它。

但如果您只想查看 lambda 的字节码,那么使用javap.