Auteur :

Loic Hermann

CTO


Publié le 02/06/2020

Temps de lecture : 2 minutes


Sommaire :


Tags: Java, Quarkus, CDI

Image par Pete Linforth de Pixabay

Quarkus : comment créer un Bean virtuel en 3 étapes

 

J’ai eu l’occasion de m’intéresser récemment au framework Quarkus qui est une implémentation JEE Microprofile pour faciliter le développement de micro-services.

Ce framework, dont la vocation est d’être extrêmement rapide (subatomic supersonic java …), permet la création d’extensions en déplaçant au maximum la logique dans le build, ce qui permet un boot ultra rapide. Pour commencer à écrire des extensions, la documentation d’origine est très claire : https://quarkus.io/guides/writing-extensions

La partie peu documentée qui m’a intéressée est la suivante : comment créer un Bean virtuel (interface injectable sans implémentation explicite) à l’aide d’une extension. Ce type de Bean _est plus connu sous le nom de _Synthetic Bean.

Etape 1 : injecter les interfaces annotées

Premièrement, notons que Quarkus n’utilise pas Weld (l’implémentation de référence de la spécification CDI) mais Arc, sa propre implémentation orientée build time.

Je vous propose de commencer par étudier un simple plugin permettant de faire du RPC au dessus de Map Hazelcast : Rcast.

Il s’agit de pouvoir injecter les interfaces annotées @RegisterRcast sans fournir d’implémentation réelle. Sous le capot, nous fournissons simplement un proxy sur l’interface pour faire l’appel RPC.

Soit l’interface suivante :

@RegisterRcast(appName = "the-app")
public interface MyService {
    String method1(String arg1);
}

Pouvant être utilisée de la sorte :

@ApplicationScopped
public class RcastExtTest {
    @Inject
    @Rcast
    MyService service;

    public void useThatService() {
        service.method1("the arg");
    }
}

Dans ce cas de figure, il faudra qu’une implémentation existe sur l’application cible (“the app” ). Mais dans notre application, nous n’aurons besoin que de l’interface.

Pour indiquer clairement notre intention d’exploiter le service proxy, nous utiliserons le _qualifier _@Rcast. Sur le service cible, par exemple, cela évitera d’injecter le proxy dès lors que nous souhaitons obtenir directement l’implémentation.

Je vous invite ensuite à lire la documentation de Quarkus relative à la création de la coquille du projet. Suite à quoi vous devriez avoir deux modules dans votre projet : “runtime” et “deployment”. Le module “deployment” contiendra l’ensemble de la logique qui sera exécutée lors du build.

Etape 2 : le BeanCreator

Nous allons d’abord définir la classe servant de modèle pour nos synthetic beans. Celle ci doit se trouver dans le module “deployment” de votre projet et implémenter l’interface BeanCreator :

public class RcastBeanCreator implements BeanCreator<Object> {
    private static final Logger log = Logger.getLogger(RcastBeanCreator.class);

    @Override
    public Object create(CreationalContext<Object> creationalContext, Map<String, Object> param) {
        Class<?> clazz = null;
        try {
            clazz = Class.forName((String) param.get("clazz"));
        } catch (ClassNotFoundException e) {
            log.error("cannot find class " + param.get("clazz"));
        }
        String appName = (String) param.get("appName");
        BeanManager manager = Arc.container().beanManager();
        Bean<?> bean = manager.resolve(manager.getBeans(RcastProvider.class));
        if(bean == null){
            log.error("Cannot find any remote provider implementation");
            return null;
        }
        RcastProvider provider = (RcastProvider) manager.getReference(bean, bean.getBeanClass(), manager.createCreationalContext(bean));
        return provider.getInstance(clazz, appName);
    }
}

Notons que je récupère ici un bean CDI (RcastProvider) en appelant le beanManager. Si nous n’injectons pas quelque part RcastProvider dans l’application, il faudra qu’il soit annoté par @Unremovable afin qu’Arc ne le supprime pas lors de la création du contexte.

Etape 3 : le BeanProcessor

Nous avons maintenant besoin d’une classe _Processor _ dans le module “deployment”. Cette classe contiendra les étapes (@BuildStep) nécessaires à la construction de nos Beans virtuels.

D’abord, nous allons nous servir de l’index jandex afin de lister les interfaces annotées par @RegisterRcast.

Nous allons donc créer la méthode suivante dans notre class Processor :

Nous pouvons maintenant utiliser notre méthode et notre modèle au sein d’une BuildStep (méthode annotée au sein de la classe Processor, servant d’étape de construction, ces méthodes sont exécutées automatiquement par Quarkus).

    private Map<DotName, ClassInfo> getInterfaces(IndexView index) {
        Map<DotName, ClassInfo> interfaces = new HashMap<>();
        for (AnnotationInstance annotation : index.getAnnotations(RcastExtProcessor.REGISTER_RCAST)) {
            AnnotationTarget target = annotation.target();
            ClassInfo theInfo;
            if (target.kind() == AnnotationTarget.Kind.CLASS) {
                theInfo = target.asClass();
            } else if (target.kind() == AnnotationTarget.Kind.METHOD) {
                theInfo = target.asMethod().declaringClass();
            } else {
                continue;
            }
            interfaces.put(theInfo.name(), theInfo);
        }
        return interfaces;
    }

Vous pouvez dès à présent injecter l’interface que nous avons créée au début de ce projet en ajoutant l’extension en dépendance de votre projet.

Thanks to Alexandre Lewandowski