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

时间:2014-05-26 01:34:21

标签: java java-bytecode-asm

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

2 个答案:

答案 0 :(得分:11)

编辑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)代码 - 如果没有找到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查看班级c.getName()的名称,我们会看到类似defining.class.package.DefiningClass$$Lambda$x/y的内容,其中xy是数字,现在我们可以理解为什么以上不起作用 - 类路径上没有这样的资源..

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

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

幸运的是,我们有一个名为ByteBuddy的库已经使用这个库创建了这样的代理 - 以下内容将起作用(如果你是一个maven用户,包括byte-buddy-dep和byte-的依赖关系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的私有方法,它提供了类{{1}的实例}}。此实例可用于检索使用SerializedLambda生成的实际静态方法。

所以,举例来说,有两种方法可以使用&#34; Serializable Lambda&#34;:

LambdaMetafactory

在上面的示例中,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

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

  

public String getImplClass():   获取包含实现方法的类的名称。   返回:   包含实现方法的类的名称

     

public String getImplMethodName():   获取实现方法的名称。   返回:   实现方法的名称

这意味着您现在可以使用ASM来读取包含lambda的类,获取实现lambda的方法并修改/读取它。

您甚至可以使用以下代码获得lambda的反射版本:

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

答案 1 :(得分:3)

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

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