如何使用 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
其中 x
和 y
是数字,现在我们可以理解为什么上面的代码不起作用了——类路径上没有这样的资源。 .
虽然 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 documentation 和 Instrumentation 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!");
}
}
在上面的示例中,oneWay
和 anotherWay
都会生成一个 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/