byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

How to generate multiple static fields and intialize them in static initializer correctly?

Open FrankChen021 opened this issue 3 years ago • 3 comments

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() {
    }
}

FrankChen021 avatar Feb 20 '22 14:02 FrankChen021

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

FrankChen021 avatar Feb 22 '22 07:02 FrankChen021

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.

raphw avatar Feb 22 '22 10:02 raphw

Thank you @raphw

FrankChen021 avatar Feb 22 '22 11:02 FrankChen021