使用Javassist或ASM拦截字段访问

时间:2014-05-24 00:03:52

标签: java java-bytecode-asm javassist bytecode-manipulation

我熟悉使用代理拦截方法调用的各种方法,但我想知道是否有办法使用像Javassist或ASM这样的库检测某些代理的字段访问/解除引用?例如:

void detectFieldName(Function<Foo, Supplier<String>> f) {
  Foo fooProxy = createFooProxy();
  f.apply(fooProxy);
}

detectFieldName((Foo foo) -> foo.bar);

理想情况下,我想知道名为bar的字段已取消引用。

2 个答案:

答案 0 :(得分:2)

查看更新后的用例:lambdas被置于合成(编译器生成的)方法中,其中一个函数对象通过生成的方法转发接口调用(我还没有仔细研究过如何实现它,但是我认为Brian Goetz已经谈过它了。您可以查看该方法的字节码(从类文件加载;某些ASM示例代码执行此操作)并读取字段访问权限。仪器不是必需的。

请注意,您无法创建代理以查看字段访问权限;字段访问是在lambda方法中执行的(或者更一般地,在加载字段的情况下),而不在Foo中执行任何代码。事实上,如果你想要的只是获得字段名称,你甚至不需要打电话给lambda,如果所有你使用Foo代理都是电话,你就不需要了代理人。


我不知道有任何方法拦截字段访问,就像java.lang.reflect.Proxy进行拦截方法调用一样容易。

getfieldputfield字节码使用编码类和字段名称的符号描述符,因此您可以使用Java agent在每次加载和存储之前或之后添加方法调用正在加载/存储的字段名称,对象和值。 (如果您只对某个字段的子集感兴趣,例如特定类的所有字段,则效果最佳。)根据您的需要,您可能还必须通过使用{{1}来识别对字段的反射访问由java.lang.reflect.Field等返回的句柄(可能涉及过程间分析或关于用于构建字段名称的字符串操作的推理等)。您也可以在&#34;之前尝试仪表。该库调用了一些特定于JVM的本机功能,但这会将您绑定到一个JVM实现,如果JVM内部化(特殊情况下代码生成)反射调用,则可能会跳过您的检测。

如果您愿意编写C代码,则可以使用JVM工具界面watched field functions。这似乎是获取信息的最简单方法,但是使用有趣的Java级别的东西更难(尽管您可以从JVMTI回调到Java支持库)。

答案 1 :(得分:1)

没有重大黑客,这是不可能的。 Java中的字段访问不是动态绑定的。这意味着,对字段的任何读取或写入都会硬编码到所有使用类中。使用方法代理,可以使用覆盖方法来确定行为的事实。对于拦截字段访问,需要拦截使用字段的类。某些库通过使用合成访问器方法替换字段访问来模拟此行为。然而,这需要在整个项目中重新定义所有相关类的构建时间。

至于你的例子,理论上你可以使用像ASM这样的工具从lambda表达式中提取所需的信息。但是,请注意,lambda表达式的代码将被提取到使用lambda表达式的方法的类的方法中。您可能无法找出实际上包含您的 lambda的方法,但调用表达式的字节代码看起来只是这样:

InvokeDynamic #0:accept:(LFoo;)Ljava/util/function/Function;

如您所见,字节代码只包含可能不明确的签名。否则,您当然可以将lambda表达式的逻辑复制到一个新类中,您可以在其中更改字段访问的逻辑。由于lambda是按定义接口的,因此创建这样一个新类实际上相当容易。但方法检测的问题仍然存在。