byte-buddy
byte-buddy copied to clipboard
How to generate multiple static fields and intialize them in static initializer correctly?
I'm adding multiple static fields to a class and initializing these fields in the static initializer. But found that only the last added fields was initialized. Is there something wrong with my code?
public class ByteBuddyTest {
@Test
void testGenerateMultipleField() throws IOException {
DynamicType.Builder<?> builder = new ByteBuddy().subclass(Base.class).name("Child");
// add multiple fields and initialize each field in the static initializer
for (int i = 0; i < 5; i++) {
builder = generateFields(builder, "field" + i);
}
builder.make().saveIn(new File("/tmp"));
}
private DynamicType.Builder<?> generateFields(DynamicType.Builder<?> builder, String fieldName) {
// Generator.generate()
MethodDescription getInterceptorMethod = new TypeDescription.ForLoadedType(Generator.class).getDeclaredMethods().filter(named("generate")).getOnly();
return builder.defineField(fieldName, String.class, ACC_STATIC)
.invokable(ElementMatchers.isTypeInitializer())
// call Generator.generate() to assign the static field
.intercept(MethodCall.invoke(getInterceptorMethod).setsField(named(fieldName)));
}
public static class Generator {
public static String generate() {
return RandomString.make();
}
}
public static class Base {
}
}
By disassembling the generated class, I found that only the last field, field4
was assigned. field0
to field3
had no assigment.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import com.example.test.ByteBuddyTest.Base;
import com.example.test.ByteBuddyTest.Generator;
public class Child extends Base {
static String field0;
static String field1;
static String field2;
static String field3;
static String field4 = Generator.generate();
public Child() {
}
}
I finally did it by chaining the initialization method call together
Implementation.Composable initializers = null;
for (int i = 0; i < 5; i++) {
String field = "field" + i;
builder = generateFields(builder, field);
if (initializers == null) {
initializers = MethodCall.invoke(getInterceptorMethod).setsField(named(field));
} else {
initializers = initializers.andThen(MethodCall.invoke(getInterceptorMethod).setsField(named(field)));
}
}
builder.invokable(ElementMatchers.isTypeInitializer())
.intercept(initializers)
.make().saveIn(new File("/tmp"));
Yes, this is the right approach. Alternatively, you can register a ByteCodeAppender
via the DynamicType.Builder.initializer
method which is not representing a unique match.
Thank you @raphw