如何让 ByteBuddy 构建 class 实现并初始化成员变量?

How can I get ByteBuddy to build a class implementation and initialise the member variables?

我一直在广泛搜索但没有成功,并且在示例中找不到答案。

与以下成员有一个 class:

public class Foo {
  public String name;
  public Long age;
}

我想构建这个 class 的新实现,它通过委托给一些 initliaiser class.

来初始化成员变量
Bar dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
                .subclass(Foo)
                .initializer(new BarInit())
                .make()
                .load(Foo.class.getClassLoader(), WRAPPER)
                .getLoaded()
                .newInstance();

也创造了

public class BarInit implements LoadedTypeInitializer {
    @Override
    public void onLoad(Class<?> type) {
       Field[] fields = type.getDeclaredFields();
       // fields is empty?!
    }

    @Override
    public boolean isAlive() {
        return true;
    }
}

我想我已经代码盲了。我需要一个提示。

所以在一些提示之后我继续

public class Foo {

    public Foo() {
    }

    public Foo(Bar qClass) {
        this();
    }

  public String name;
  public Long age;
}

FooBar dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
        .subclass(Foo)
        .defineConstructor(PUBLIC)
                .withParameter(Bar.class, "initClass")
                .intercept(SuperMethodCall.INSTANCE
                .andThen(MethodDelegation.to(new Interceptor())))
        .make()
        .load(getClass().getClassLoader())
        .getLoaded()
        .getDeclaredConstructor(Bar.class)
        .newInstance(new Bar());

结果 java.lang.IllegalStateException: Cannot call super (or default) method

拦截器有

public void intercept(@Origin Constructor m) {
    System.out.println("Intercepted: " + m.getName());
}

现在是'works',虽然我还没有弄清楚初始化部分!最终,class Foo 现在有了我不想要的构造函数。

但是 - 按住按钮!

我experimented/read进入凌晨,想出了:

    public class Foo {

        public Foo() {
        }

      public String name;
      public Long age;
    }

    FooBar dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
            .subclass(Foo)
            .defineConstructor(PUBLIC)
                    .withParameter(Bar.class)
                    .intercept(MethodCall.(Foo.class.getDeclaredConstructor())
                    .andThen(MethodDelegation.to(new Interceptor())))
            .make()
            .load(getClass().getClassLoader())
            .getLoaded()
            .getConstructor(Bar.class)
            .newInstance(new Bar());


    public void intercept(Bar cls) {
        System.out.println("Intercepted: " + cls);
    }

剩下的就是发现如何获取对正在构建的实例的引用,该实例可供 intercept()

使用

您的字段是由 Foo 定义的实例字段。如果您定义了一个 LoadedTypeInitializer,它将初始化 Foo 的子 class,它没有声明这两个有问题的字段。因此数组为空。此外,类型初始值设定项不会带您到任何地方,因为它用于初始化类型,而不是实例。

您可能想要定义一个构造函数并从那里设置字段。请记住,任何构造函数都需要首先调用同一 class 的超级构造函数或另一个构造函数。 (为此请查看 SuperMethodCall

经过大约 20 个小时的 trial-and-error 阅读 40 多个不同的 'solutions' 到类似但最终不同的问题,我得出了以下结论,这似乎符合我的意图。

public class Foo {
      public String name;
      public Long age;
    }

public class Bar {
      public String name() {
          return "Name";
      }
      public Long age() {
          return 21;
      }
    }

public class Demo {

    FooBar dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
            .subclass(Foo)
            .defineConstructor(PUBLIC)
                    .withParameter(Bar.class)
                    .intercept(MethodCall.(Foo.class.getDeclaredConstructor())
                    .andThen(MethodDelegation.to(this)))
            .make()
            .load(getClass().getClassLoader())
            .getLoaded()
            .getConstructor(Bar.class)
            .newInstance(new Bar());

    public void intercept(@This Object thiz, @Argument(0) Bar bar) {
        thiz.name = bar.name();
        thiz.age = bar.age();
    }
}

我希望这对其他一直在挑灯夜战的可怜人有所帮助。