如何使用ASM从lambda表达式的主体读取字节码指令?
答案 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
的内容,其中x
和y
是数字,现在我们可以理解为什么以上不起作用 - 类路径上没有这样的资源..
虽然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 documentation和Instrumentation 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
会更简单。