使用 Byte Buddy 拦截对 Java 8 个 lambda 表达式的调用

Intercepting calls to Java 8 lambda-expressions using Byte Buddy

我尝试使用 Byte Buddy AgentBuilder 拦截对方法的调用和对 Java 8 个 lambda 表达式的调用,如下所示:

static {
  final Instrumentation inst = ByteBuddyAgent.install();
  new AgentBuilder.Default()
        .type(ElementMatchers.nameContainsIgnoreCase("foo"))
        .transform((builder, typeDescription) ->
                builder.method(ElementMatchers.any())
                        .intercept(MethodDelegation.to(LogInterceptor.class)))
        .installOn(inst);
}

public static class LogInterceptor {
  @RuntimeType
  public static Object log(@SuperCall Callable<?> superCall) throws Exception {
    System.out.println("yeah...");
    return superCall.call();
  }
}

我正在使用 Byte Buddy v0.7.1。

可以拦截以下Runnable(匿名class):

FunnyFramework.callMeLater(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from inner class");
    }
});

当然还有对定义为正常(非匿名)class对象的任何调用。但拦截不适用于 lambda 表达式,例如:

FunnyFramework.callMeLater(() -> {
    System.out.println("Hello from lambda");
});

如何拦截 lambda 表达式调用?据我所知,Byte Buddy 中没有 LambdaInterceptor 这样的东西。

Java 虚拟机不允许转换表示 lambda 表达式的 class 文件。 类 表示 lambda 表达式由所谓的 anonymous class loaders (not to be confused with classical anonymous classes) 加载,它继承了另一个 class 的安全上下文,例如使用匿名 class 加载器加载的 class 将加载的 class 绑定到另一个 class Foo 可以访问 private 方法 Foo。此加载使用 sun.misc.Unsafe API.

显式发生

Byte Buddy 挂钩到 Java instrumentation API,它允许 ClassFileTransformers 的应用程序挂钩到 ClassLoaders 加载过程。由于 匿名 class 加载器 在常识中不被视为 ClassLoader,因此仪器 API 不允许此类仪器,因此禁止仪器lambda 表达式。

这对于某些用例来说当然是不幸的,但在大多数现实生活中的应用程序中,并没有真正需要检测 lambda 表达式。例如,许多现实世界的检测应用于使用给定注释进行注释的方法,而这些方法不可能应用于 lambda 表达式或比功能接口更复杂的 classes。


更新:使用 Byte Buddy 1.1.0 版,可以检测表示 lambda 表达式的 classes。为此,Byte Buddy 检测了 JVM 的 LambdaMetafactory 并用自定义定义替换了 class 生成。要激活此功能,请在构建器中执行以下步骤:

new AgentBuilder.Default()
  .with(LambdaInstrumentationStrategy.ENABLED)

请注意,这仅适用于 OpenJDK 8u40,在以前的版本中,有一个与 invokedynamic 调用站点相关的错误阻止了它的工作。