Introduction : L'évolution de la métaprogrammation en Java
Traditionnellement, l'inspection des métadonnées (comme les annotations) se faisait au runtime via la Réflexion. Approche puissante mais qui introduit des pénalités de performance et des risques d'erreurs cachées. L'approche moderne en Java est la Génération de Code à la Compilation, notamment via l'Annotation Processing , qui permet de transformer les métadonnées en classes sources compilées.
J'ai ajouté cette possibilité à la librarie Formatic pour la gestion des formulaires par cette transition, révélant clairement les forces et faiblesses des deux modèles.
1. Le Modèle Traditionnel : Réflexion (Runtime)
Avant l'Annotation Processing, nos gestionnaires (TextInputHandler, SelectInputHandler, etc.) utilisaient la réflexion pour lire les annotations lors de l'exécution de l'application et construire les objets FormFieldMetadata.
Avantages de la Réflexion :
- Simplicité de configuration : Pas besoin d'outillage spécialisé (comme un Processor et JavaPoet).
- Flexibilité : Idéale pour les besoins très dynamiques ou l'intégration tardive où le contexte de la JVM est essentiel (ex: trouver un service spécifique via un conteneur IoC).
- Débogage facile : Le code s'exécute dans l'IDE comme n'importe quelle autre classe Java.
Inconvénients de la Réflexion :
- Pénalité de performance (Runtime) : La réflexion est notoirement lente, car elle contourne le typage fort et l'optimisation du JIT (Just-In-Time) Compiler. Pour des centaines de champs, le temps de construction des métadonnées peut devenir perceptible. Cependant, avec le cache, il est possible de diminuer considérablement la pénalité.
- Erreurs masquées : Les fautes de frappe dans les noms de méthodes ou les attributs d'annotation (ex: optionsProvider = "getcitites" au lieu de "getCities") ne sont détectées qu'au runtime (une erreur 404 dans votre application finale).
- Problèmes de sécurité (si applicable) : Peut être restreinte dans certains environnements sécurisés.
2. Le Nouveau modèle : annotation processing (Compile-Time)
Avec notre implémentation du FormMetadataProcessor, nous avons déplacé toute la logique de construction des métadonnées vers l'étape de compilation. L'annotation agit comme une instruction pour le compilateur de générer un nouveau fichier source (ex: EditorFormMetadata.java).
La librairie JavaPoete est utilisé pour faciliter la création de cette classe.
Comme vous pouvez remarquer, c'est pas le code le plus aisé à lire.
Avantages de l'annotation processing :
- Performance maximale (Runtime) : Le gain le plus significatif. La construction des métadonnées est transformée en lignes de code Java standard et fortement typées. La List<FormFieldMetadata> est créée par une simple méthode statique (souvent getMetadata()) sans aucune réflexion, ce qui la rend instantanée à l'exécution.
- Détection d'erreurs à la compilation : Le Processor garantit la validité structurelle du code généré. Si la logique du Processor est erronée (ex: elle tente d'utiliser FormFieldType.INPUT au lieu de FormFieldType.TEXT), l'application ne compile pas, assurant une qualité et une robustesse accrues.
- Simplicité du code client : Le framework n'a plus besoin de code complexe de handler basé sur la réflexion pour interpréter les annotations. Il appelle simplement une méthode statique de la classe générée.
- Cache : Les outils de build (Gradle, Maven) mettent en cache les résultats du Processor, accélérant les compilations subséquentes.
Inconvénients de l'annotation processing :
- Complexité du développement (le Processor) : Le code du FormMetadataProcessor est intrinsèquement plus difficile à écrire et à maintenir. Il nécessite la maîtrise de l'API javax.lang.model et d'outils tiers comme JavaPoet.
- Débogage complexe : Le débogage d'un Processor nécessite une configuration spéciale et l'attachement d'un débogueur à la JVM du compilateur (souvent via un build à distance sur le port 5005).
- Limitation des entrées : Le Processor est aveugle à tout ce qui nécessite un état de runtime (API, base de données, injection de dépendances). C'est pourquoi, pour les options dynamiques (optionsProvider), nous ne générons que le nom de la méthode, laissant la résolution des données au runtime de l'application.
- Duplication de la logique : La logique de construction des métadonnées doit être réécrite du modèle de handler réflexif vers le modèle de génération de code JavaPoet.
Conclusion : Le choix de la performance
Bien que le coût initial de développement d'un Processor soit plus élevé, le gain en performance d'exécution est d'environ 10ms sur 1 millions d'appel. Ce choix technique pourrait être intéressant pour toute librairie où la construction répétée d'objets est critique.
Code de la branche: https://github.com/marccollin/formatic/tree/annotation_processor
Aucun commentaire:
Enregistrer un commentaire