javabyte-buddy

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


I have been searching extensively without success, and can't find the answer in the examples.

Having a class with the following members:

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

I want to build a new implementation of this class that initialises the member variables by delegating to some initliaiser class.

Bar dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
                .subclass(Foo)
                .initializer(new BarInit())
                .make()
                .load(Foo.class.getClassLoader(), WRAPPER)
                .getLoaded()
                .newInstance();

also created

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

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

I think I've gone code-blind. I need a hint.

So after some hints I moved on to

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());

results in java.lang.IllegalStateException: Cannot call super (or default) method

The interceptor has

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

and now it 'works', although I'm still to figure out the initialisation part! And ultimately, the class Foo now has constructors I didn't want.

But - HOLD THE PRESS!

I experimented/read into the small hours and came up with:

    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);
    }

What remains is to discover how to get a reference to the instance being constructed being made available to intercept()


Solution

  • After some 20 hours of trial-and-error based on reading 40+ different 'solutions' to similar - but ultimately different - problems, I arrived at the following, which seems to do what I intended.

    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.invoke(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();
        }
    }
    

    I hope this helps some other poor soul who has been burning the midnight oil.