quarkusquarkus-extension

Quarkus: Generate bean with a generic bean dependency passed at constructor


I would like to know how I can generate a bean having a dependency to an another bean (using generic Type).

I made a sample here: https://github.com/dcdh/quarkus-hello-world-playground

I've got this bean

@Singleton
public class DefaultHelloWorld extends HelloWorld<String> {
    public DefaultHelloWorld(final HelloWorldDependency<String> helloWorldDependency) {
        super(helloWorldDependency);
    }

    @Override
    public String sayHello() {
        return "DefaultHelloWorld";
    }
}

And I am using this kind of code to generate the java code

@BuildStep(onlyIf = ShouldGenerateBean.class)
    void helloWorldGenerator(final BuildProducer<GeneratedBeanBuildItem> generatedBeanBuildItemBuildProducer) {
        final ClassOutput beansClassOutput = new GeneratedBeanGizmoAdaptor(generatedBeanBuildItemBuildProducer);
        final ClassCreator beanClassCreator = ClassCreator.builder().classOutput(beansClassOutput)
                .className(HelloWorld.class.getName() + "Generated")
                .signature(
                        SignatureBuilder.forClass()
                                .setSuperClass(
                                        Type.parameterizedType(
                                                Type.classType(HelloWorld.class),
                                                Type.classType(String.class))))
                .build();
        beanClassCreator.addAnnotation(Singleton.class);
        beanClassCreator.addAnnotation(DefaultBean.class);
        // constructor
        final MethodCreator constructor = beanClassCreator.getMethodCreator(MethodDescriptor.INIT, void.class,
                String.format("%s<%s>", HelloWorldDependency.class.getName(), String.class.getName()));
        constructor.setModifiers(Modifier.PUBLIC);
        ResultHandle supportHandle = getFromCDI(constructor, String.format("%s<%s>", HelloWorldDependency.class.getName(), String.class.getName()));
        constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(HelloWorld.class, HelloWorldDependency.class), constructor.getThis(), supportHandle);
        constructor.returnValue(null);

        // sayHello
        final MethodCreator clazzMethod = beanClassCreator.getMethodCreator("sayHello", Object.class);
        clazzMethod.setModifiers(Opcodes.ACC_PUBLIC);
        clazzMethod.returnValue(clazzMethod.load("HelloWorldGenerated"));
        beanClassCreator.close();
    }

    // https://github.com/quarkiverse/quarkus-langchain4j/blob/main/core/deployment/src/main/java/io/quarkiverse/langchain4j/deployment/AiServicesProcessor.java#L627 :)
    private ResultHandle getFromCDI(final MethodCreator mc, final String className) {
        // Arc.container().instance(className).get();
        final ResultHandle containerHandle = mc.invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class));
        final ResultHandle instanceHandle = mc.invokeInterfaceMethod(
                MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class, Class.class,
                        Annotation[].class),
                containerHandle, mc.loadClassFromTCCL(className),
                mc.newArray(Annotation.class, 0));
        return mc.invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle);
    }

If you have a look to the previous commit it is working fine when the HelloWorldDependency dependency is not a generic.

To make it work I am using Arc to retrieve the bean. However, the bean is resolved at runtime and so Generic is no more available. Should I generate and use Qualifier ?

Is there an another way to generate bean using a Quarkus build item ? I was expected to make it works without using Arc but it seems that it is not possible.

Regards, Damien


Solution

  • So when I look at the quarkus-hello-world-playground project it seems that the gizmo code for the HelloWorldGenerated constructor is the problem.

    First, you need to set the method signature to reflect the generic parameters. Also the HelloWorldPlaygroundProcessor#getFromCDI() is useless because you don't need to lookup the bean programmatically if using constructor injection - the bean instance is injected in the constructor argument.

    The code should look like:

    MethodCreator constructor = beanClassCreator.getMethodCreator(MethodDescriptor.INIT, void.class, HelloWorldDependency.class);
    constructor.setSignature(
                    SignatureBuilder.forMethod()
                            .addParameterType(Type.parameterizedType(Type.classType(HelloWorldDependency.class), Type.classType(String.class)))
                            .setReturnType(Type.voidType())
                            .build());
    constructor.setModifiers(Modifier.PUBLIC);
    constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(HelloWorld.class, HelloWorldDependency.class), constructor.getThis(), constructor.getMethodParam(0));
    constructor.returnVoid();