Chain/transform 使用 ByteBuddy 调用方法

Chain/transform method calls with ByteBuddy

使用 ByteBuddy,我可以通过调用另一个实例方法并运行形成结果来实现一个实例方法吗?

例如(玩具示例):

public abstract class Foo {
  public String bar() {
    return "bar";
  }

  public abstract int baz();
}

鉴于上述情况,我能否实现 baz,使其调用 bar() 和 returns returned 字符串的长度?即,就好像它是:

public int baz() {
  return bar().length();
}

我天真地尝试了以下方法:

Method bar = Foo.class.getDeclaredMethod("bar");
Method baz = Foo.class.getDeclaredMethod("baz");

Method length = String.class.getDeclaredMethod("length");

Foo foo = new ByteBuddy()
  .subclass(Foo.class)
  .method(ElementMatchers.is(baz))
  .intercept(
    MethodCall.invoke(bar)                 // call bar()...
      .andThen(MethodCall.invoke(length))  // ... .length()?
  ).make()
  .load(Foo.class.getClassLoader())
  .getLoaded()
  .newInstance();

System.out.println(foo.baz());

但是,我认为 andThen() 是在第一次调用的 return 值上调用的,这似乎是错误的;它看起来像是在生成的实例上调用的。

Exception in thread "main" java.lang.IllegalStateException: 
  Cannot invoke public int java.lang.String.length() on class Foo$ByteBuddy$sVgjXXp9
    at net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation
    .invoke(MethodCall.java:1667)

我也试过一个拦截器:

class BazInterceptor {
  public static int barLength(@This Foo foo) {
    String bar = foo.bar();
    return bar.length();
  }
}

与:

Foo foo = new ByteBuddy()
  .subclass(Foo.class)
  .method(ElementMatchers.is(baz))
  .intercept(MethodDelegation.to(new BazInterceptor()))
  // ...etc.

这个 运行,但产生了无意义的结果 870698190,并且设置断点 and/or 在 barLength() 中添加打印语句表明它永远不会被调用;很明显,我也没有正确理解拦截器或 @This

如何让 ByteBuddy 调用一种方法,然后根据其 return 值调用另一种方法?


Per : BazInterceptor 工作如果 :

我怀疑 870698190 委托给了 BazInterceptor 实例的 hashCode(),虽然我没有实际检查过。

您使用实例作为拦截器,这意味着实例方法是首选(可能根本不接受静态方法)。有一个实例方法与您的 int baz() 方法的签名匹配,它是 int hashCode()。您得到的数字是 new BazInterceptor() 实例的哈希码。

我知道的选项:

  • barLength 中删除 static 这样它实际上将用于拦截。
  • 添加 class 作为拦截器 .intercept(MethodDelegation.to(BazInterceptor.class))

我更喜欢第二个选项,因为您没有使用 BazInterceptor 实例的任何 fields/state。

目前在 Byte Buddy 中没有很好的方法,但这将是一个很容易添加的功能。你可以track the progress on GitHub。等我有时间再补充。

如果您现在想实现这样的链式调用,您可以在 Java 代码中实现它们并使用 Advice 组件内联此代码。或者,您可以通过基于 MethodInvocation 实例创建自己的 ByteCodeAppender 来更明确地编写字节代码,但是您必须手动加载参数。