【发布时间】:2014-05-26 01:34:20
【问题描述】:
如何使用 ASM 从 lambda 表达式的主体中读取字节码指令?
【问题讨论】:
如何使用 ASM 从 lambda 表达式的主体中读取字节码指令?
【问题讨论】:
EDIT 01-08-2016:我使用 SerializedLambda 类添加了另一个方法,它不需要 3 方软件(即 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 类是在运行时通过 @987654329 生成的@class,因此获取字节码的正常方法不起作用:
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 用户,请同时包含 byte-buddy-dep 和 byte-buddy- 的依赖项)您的 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 的私有方法,它提供类 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,我们会发现以下方法:
公共字符串 getImplClass(): 获取包含实现方法的类的名称。 回报: 包含实现方法的类名
公共字符串 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");
}
【讨论】:
SerializedLambda 部分对我非常有帮助。你应该把它拉到答案的顶部。
lambda 编译为static method with a synthetic name。因此,要使用 ASM 读取代码,您需要对方法名称进行逆向工程……然后像任何其他方法一样读取它。
但如果你只是想查看 lambda 的字节码,使用javap 会更简单。
【讨论】: