Java annotation processor: bien ou non?

110 views
Skip to first unread message

jeremie...@unblu.com

unread,
Apr 24, 2019, 4:14:05 AM4/24/19
to lescastcodeurs
Dans l'épisode 208 sur Quarkus, à la minute 38 (au moment ou Emmanuel fait une comparaison avec micronaut.io), il est question des "java annotation processors":

Si j'essaye de prendre en note ce qui est dit:

* C'est difficile à coder
* L'interaction avec l'IDE ne marche pas toujours (selon les jours)
* Ca ne passe pas à l'échelle quand on commence à avoir une centaine ou plus d'annotation à traiter, pour quelque chose qui doit tourner à chaque compilation.

Du coup Quarkus fait autre chose après la compilation Java.

---

J'ai le souvenir vague que dans mon ancienne boite, on était retourné sur un outil custom basé sur Eclipse JDT (headless) au lieu d'utiliser les annotation processors. 
Mais j'avais suivit le problème de très loin (discussion à la machine à café), donc je n'ai rien de tangible.

---

Tout à l'heure un collègue me disait qu'un projet à succès comme MapStruct est basé là dessus.
Je connais très mal la galaxie des projets Spring donc je n'ai pas trop d'avis sur ce projet.

---

Est ce que quelqu'un a des articles/études à recommender sur le sujet?

C'est quoi les bon cas d'usage? Et les cas où il vaut mieux faire autre chose?


Merci d'avance pour vos messages.


On Tuesday, 26 March 2019 09:28:35 UTC+1, Emmanuel Bernard wrote:
Dans cet épisode, Arnaud et Antonio discutent de Quarkus avec Emmanuel Bernard.


Emmanuel

Cédric Champeau

unread,
Apr 24, 2019, 4:45:10 AM4/24/19
to lescast...@googlegroups.com
Un des gros problèmes des annotation processors, c'est qu'il y a un certain nombre d'auteurs qui ne comprennent pas que leurs utilisateurs souffrent de leurs perfs catastrophiques.

Dans Gradle on a ajouté la possibilité d'avoir des annotation processors incrémentaux, qui disent ce qu'ils font, et on a même des PRs, dont certaines ont été refusées parce que lesdits auteurs ne voient pas l'intérêt (c'est qd même con d'ignorer sa base utilisateur) : https://github.com/gradle/gradle/blob/master/subprojects/docs/src/docs/userguide/java_plugin.adoc#state-of-support-in-popular-annotation-processors

Quand il est implémenté proprement, du coup, les perfs sont excellentes. Ca implique 2 choses:

- splitter les annotations et l'implémentation du processeur dans 2 jars/artéfacts distincts (parce que que comme ça si une annotation est ajoutée mais que vous ne l'utilisez pas, on sait qu'on n'a pas besoin de recompiler, mais on sait aussi que si l'implem du processeur change, on a besoin de tout recompiler)
- dire à votre outil de build ce que fait l'annotation processor, de façon à pouvoir faire de l'incrémental (lien ci-dessus)
- utiliser un outil de build qui en tire parti (pour le coup Quarkus a tendance a réinventer la roue)

A noter que micronaut fait plusieurs choses:

1. pour Java/Kotlin, utilisation d'annotation processors
2. pour Groovy, utilisation de transfos d'arbre syntaxique

Dans les 2 cas, c'est un effort d'implémentation, mais pas monstrueux non plus. Vraiment, ce qui compte c'est le faire correctement, ou alors utiliser des AP vertueux.

--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes "lescastcodeurs".
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse lescastcodeur...@googlegroups.com.
Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse lescast...@googlegroups.com.
Visitez ce groupe à l'adresse https://groups.google.com/group/lescastcodeurs.
Pour obtenir davantage d'options, consultez la page https://groups.google.com/d/optout.

Guillaume Laforge

unread,
Apr 24, 2019, 4:49:26 AM4/24/19
to lescast...@googlegroups.com
Je discutais avec Graeme Rocher, le lead de Micronaut, qui a justement pris le parti, lui, d'utiliser les annotation processors.
Je vais paraphraser les avantages / inconvénients qu'il voyait entre l'approche annotation processor vs manipulation de bytecode :

D'autres plateformes ou projets, comme par exemple Android, sont passés par la casse annotation processor, cela atteste que ça ne doit pas être une si mauvaise solution non plus :-)
Sans annotation processor, on doit travailler directement avec le bytecode, et c'est moins pratique pour rapporter les erreurs au niveau du code source.
Alors qu'avec des annotation processors qui travaille au niveau du code source, on peut être très précis et pointer l'endroit exact au niveau du source où le problème se situe.
S'il y a des soucis avec l'approche manipulation de bytecode, les utilisateurs se taperont des NoSuchMethodErrors au runtime, et ça va être coton de débugguer les problèmes du "compilateur" de Quarkus. 
Il faut une séparation propre entre le path de l'annotation processor, celui de la compilation, et celui du runtime; là où je crois Quarkus mélange tout dans un gros classpath. Sinon possible d'avoir des soucis de conflit de classpath. Le soucis va aussi être pour le futur, pour upgrader de version en version, tout en gardant la compatibilité. Mais peut-être que ce n'est pas trop un problème si on recompile de toute façon tout... mais à voir tout de même s'il n'y a pas de soucis potentiels avec les dépendances de ton projet. 

Guillaume

--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes "lescastcodeurs".
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse lescastcodeur...@googlegroups.com.
Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse lescast...@googlegroups.com.
Visitez ce groupe à l'adresse https://groups.google.com/group/lescastcodeurs.
Pour obtenir davantage d'options, consultez la page https://groups.google.com/d/optout.


--
Guillaume Laforge
Apache Groovy committer
Developer Advocate @ Google Cloud Platform

Réda Housni Alaoui

unread,
Apr 24, 2019, 4:49:39 AM4/24/19
to lescastcodeurs
Nous avons 2 processeurs d'annotation sur nos projets. 
L'un est fourni par hibernate et génère les méta données des entités JPA. 
L'autre a été écrit par nous et permet de générer du code boilerplate.

Le support d'IntelliJ est plutôt bon.

Le reproche que je ferais concernerait plutôt le process de création d'un processeur d'annotation.
Grossièrement, les processeurs fonctionnent en 2 étapes:
1- Parsing du code existant
2- Génération de code ou de resources

Le parsing de code doit se faire avec une API qui est distincte de l'API de reflection runtime. Je trouve que c'est un peu dommage.
Cela empêche de réutiliser des librairies existantes basées sur la reflection depuis un processeur d'annotation.

Je suppose que c'est pour ce motif que quarkus ne les utilise pas.

En tout cas, j'ai hâte de lire d'autres avis concernat ce sujet :)

Emmanuel Bernard

unread,
Apr 24, 2019, 6:16:49 AM4/24/19
to lescast...@googlegroups.com


> On 24 Apr 2019, at 10:14, jeremie...@unblu.com wrote:
>
> Tout à l'heure un collègue me disait qu'un projet à succès comme MapStruct est basé là dessus.
> Je connais très mal la galaxie des projets Spring donc je n'ai pas trop d'avis sur ce projet.

MapStruct n’a aucun rapport avec Spring. C’est un développeur de chez Red Hat qui l’a monté dans son temps libre.

>
> ---
>
> Est ce que quelqu'un a des articles/études à recommender sur le sujet?
>
> C'est quoi les bon cas d'usage? Et les cas où il vaut mieux faire autre chose?

Si le code de l’appli doit voir les classes ou artifact générés (par exemple ton code applicatif mentionne une classe générée) alors l’annotation processor est la solution.

Frédéric Camblor

unread,
Apr 26, 2019, 3:31:15 PM4/26/19
to lescastcodeurs
Après utilisation intensive sur RestX, pour moi, les principaux avantages/inconvénients d'un Annotation Processor sont les suivants :

[Inconvénients/Contraintes]
  • OK il faut bien configurer son IDE pour prendre en compte les annotation processor dans le classpath (c'est une case à cocher dans intellij / dans Eclipse et un build step dans Netbeans)

  • Si en tant que consommateur, tu es mécontent du code généré, ça va être compliqué pour toi de le changer (il va falloir forker l'annotation processor et le patcher), là où, avec de la "génération one shot" (à la JHipster) ce sera beaucoup plus simple de patcher le code généré (mais attention aux montées de versions dudit générateur)

  • Il n'est pas simple de rendre "facilement customisable" un annotation processor : imaginons qu'un AP se repose sur un SPI pour customiser certains comportements, alors ces SPI ne pourront pas être fournis par le module M qui "utilise" l'AP sous peine d'avoir un problème d'oeuf/poule (les implémentations ne seront pas connues au compile time car ... pas encore compilées)
    => Les implémentations du SPI devront être "sorties" de M dans une dépendance spécifique qui sera utilisée compile-time "only" (dans le classpath de l'AP)

  • Aujourd'hui, les AP sont cantonnés à de la production de code uniquement Java à chaque "round" de compilation (par exemple, kapt, l'annotation processor Kotlin, ne va pas savoir générer du Kotlin et le re-compiler/processer lors du round suivant)
    Je croise les doigts pour que ça change dans le futur.

  • Il vaut mieux cantonner l'utilisation des AP sur des "Applications" et non des "Librairies" (en gros, sur les feuilles de ton arbre de dépendances, et non sur ses noeuds)
    Car avoir un jar contenant des classes générées sera tout sauf une bonne idée (pour plein de bonnes raisons qui sont facilement imaginables par rapport aux conflits de versions)
    => Les AP ont généralement un domaine d'intervention très "end to end"

  • Générer du code, c'est rajouter beaucoup de bytecode qui a tendance à faire "un peu tout le temps la même chose"
    => Sur de gros projets, ça peut avoir tendance à alourdir un peu la VM (classpath scanning, nombre de classes chargées dans la VM etc..)
    Je pense que cette empreinte reste toutefois ridicule par rapport aux jars qu'on aura tendance à intégrer sur nos bonnes vieilles applis de gestion

[Avantages]
  • Souvent, l'AP sert à remplacer ce qu'on ferait avec de la reflection en Java ... ça engendre des amélioration en terme :
    • de lisibilité du code : un call hierarchy peut montrer les "vrais chemins d'appel" du code généré appelant ... là où de la reflection va le masquer de par sa généricité
    • de lisibilité des call stack (un contre-exemple est typiquement les call stack Spring à base de proxies qui rajoutent à chaque traversée 3-4 niveaux d'indirections dans les stack d'appels)
    • de performance au runtime : inlining d'appel sans avoir recours à un quelconque JIT (= GraalVM friendly)

  • La "maintenance" du code généré est bien meilleure que sur des solutions de génération "one shot" (type jhipster) : sur une montée de version de l'AP, il suffit de supprimer les classes générées et de les re-générer, là où des générateurs one shot ont besoin de faire des diff sur le code généré entre une version X et une version Y au cas où le consommateur ait appliqué des changements sur le code généré.

Mon post peut donner l'impression qu'il y a beaucoup plus d'inconvénients que d'avantages, mais honnêtement les gains en perf sont vraiment importants et synergisent très bien avec Graal VM.

Je note par contre l'existence de cette feature d'annotation processing incrémental mis en avant par Cédric, c'est intéressant ! :-)

Frédéric Camblor  



--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes lescastcodeurs.
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse lescastcodeur...@googlegroups.com.
Pour envoyer un message à ce groupe, adressez un e-mail à lescast...@googlegroups.com.

Visitez ce groupe à l'adresse https://groups.google.com/group/lescastcodeurs .
Pour plus d'options, visitez le site https://groups.google.com/d/optout .
Reply all
Reply to author
Forward
0 new messages