Reputation: 618
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()
Upvotes: 2
Views: 1256
Reputation: 618
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.
Upvotes: 1
Reputation: 43972
Your fields are instance fields defined by Foo
. If you define an LoadedTypeInitializer
it will initialize the subclass of Foo
that does not declare the two fields in question. Therefore the array is empty. Also, the type initializer will not get you anywhere as it is used for initializing the type, not the instance.
You probably want to define a constructor and set the fields from there. Remember that any constructor needs to invoke a super constructor or another constructor of the same class first. (Have a look at SuperMethodCall
for that.
Upvotes: 1