Reputation: 45
How can I initiate object fields in the constructor interceptor?
I created a constructor with Byte Buddy like in the following code.
Class<?> klass = new ByteBuddy()
.subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.defineProperty("origin", CoreObject.class, true)
.defineProperty("variableNamedField", Map.class, true)
.defineConstructor(Visibility.PUBLIC)
.withParameters(CoreObject.class)
.intercept(
// Invoke Objects default constructor explicitly
MethodCall.invoke(Object.class.getConstructor())
.andThen(FieldAccessor.ofField("origin").setsArgumentAt(0))
.andThen(FieldAccessor.ofField("variableNamedField").setsValue(new HashMap<>()))
.andThen(MethodDelegation.to(new FillMapInterceptor("variableNamedField")))
//
// I have to fill the map.
// Something like this:
//
// variableNamedField.put("first", new FirstHandler(origin));
// variableNamedField.put("second", new SecondHandler(origin));
//
)
.make()
.load(CoreObject.class.getClassLoader())
.getLoaded();
First the constructor saves the parameter to the private field. Then it creates the collection. Then it calls the following interceptor to fill that collection.
class FillMapInterceptor {
private final String mapField;
public FillMapInterceptor(String mapField) {
this.mapField = mapField;
}
public void construct(@FieldValue("variableNamedField") Map<String, Handler> map, @FieldValue("origin") CoreObject coreObject){
map.put("first", new FirstHandler(coreObject));
map.put("second", new SecondHandler(coreObject));
}
}
It's better to instantiate the field variableNamedField in the interceptor because it turns out that variableNamedField fields are instantiated with the same HashMap object each time a new class instance in created. However, I can only pass the existing field to the interceptor via @FieldValue annotation. But it seems that I can't assign the field with new variable in the interceptor.
Upvotes: 1
Views: 1098
Reputation: 44032
The easiest way to do this would be to define the constructor using an Advice
class. An advice class allows you to define code using a template that is later inlined. You would use @Advice.OnMethodExit
in this case to add the code after the above Implementation
and then wrap around the code you already created above:
Advice.to(YourAdvice.class).wrap(...)
You find all information about defining advice in the javadoc by basically, you can copy paste your code above into a static method to inline it:
class YourAdvice {
@Advice.OnMethodExit
static void exit(@Advice.FieldValue("variableNamedField")
Map<Object, Object> variableNamedField) {
variableNamedField.put("first", new FirstHandler(origin));
variableNamedField.put("second", new SecondHandler(origin));
}
}
Upvotes: 1