Le niveau 1 a pour objet de faire découvrir le concept de processing d’annotations intégré au compilateur, de comprendre ce qu’est un Processor, son cycle de vie, etc…
Les exercices ci-dessous se concentrent sur l’apprentissage de la création d’un Processor
, comment le compiler, comment le déclarer auprès du compilateur (à la main et avec Maven), activer les logs du processing d’annotations, écrire un log, contrôler la compilation, …
Le première étape de l’exercice consiste à utiliser javac
en ligne de commande pour compiler un annotation processor.
Dans le répertoire exo1
se trouve le fichier StutteringHelloWorldProcessor.java
.
Cette classe est un processor car elle implémente l’interface javax.annotation.processing.Processor
. Les méthodes dont l’implémentation est nécessaire à cet exercice sont getSupportedAnnotationTypes
, init
et process
(voir leur javadoc respective).
La compilation d’un annotation processor est identique à n’importe quelle classe.
Important
|
Compilez |
Pour utiliser en annotation processor, il suffit de spécifier à javac
le nom de la ou les classes à utiliser via un argument de la ligne de commande lors de la compilation.
Important
|
Compilez Vous devez voir s’afficher les 2 lignes suivantes dans la console: Note: Hello world! Note: Hello world! |
Important
|
Maintenant, compilez en même temps Constatez que le résultat dans la console est le même. Nous allons rechercher l’explication et corriger ce bégaiement dans le prochain exercice. |
Important
|
Vérifiez qu’aucun message ne s’affiche si vous commentez l’annotation @Deprecated des classes que vous compilez (SomeDeprecatedClass et/ou SomeOtherDeprecatedClass.java ).
|
Important
|
Vérifiez que c’est aussi le cas si vous modifiez la valeur retournée par la méthode getSupportedAnnotationTypes .
|
Important
|
Vérifiez que les messages s’affichent si getSupportedAnnotationTypes retourne *
|
Vous pouvez vérifier le comportement de javac
si vous spécifiez le nom d’un annotation processor qui ne se trouve pas dans le class path.
Important
|
question subsidiaire : trouvez au moins un autre moyen de désactiver l’annotation processing avec javac .
|
Lors de l’étape 2 de l’exercice 1, vous vous êtes sûrement demandé pour quelle raison deux lignes s’affichent et sûrement constaté que ce n’est pas lié au nombre de fichiers compilés.
Ce comportement est en réalité lié au mécanisme de "round" de l’annotation processing et la manière dont nous avons écrit la méthode process
de StutteringHelloWorldProcessor.java
.
Dans le répertoire exo2
se trouve le fichier HelloWorldProcessor.java
.
Cette classe est un processor mais notez qu’elle n’implémente pas l’interface Processor
directement, elle étends la classe abstraite javax.annotation.processing.AbstractProcessor
. Cette classe abstraite permet de simplifier l’écriture d’un Processor.
Une option de javac
permet d’activer les logs des rounds d’annotations processing. Consultez la doc de javac
pour l’identifier.
Important
|
Compilez HelloWorldProcessor.java dans le répertoire exo2 puis SomeDeprecatedClass en activant les logs des rounds. (indice: documentation de javac)
|
Constatez qu’il y a deux rounds, ce qui explique l’affichage du "Hello world!" en doublon.
Une autre option permet d’afficher pour chaque round quels sont les processors que javac fait participer.
Important
|
Compilez SomeDeprecatedClass en activant les deux options.
|
Le cycle de vie d’un objet implémentation l’interface Processor
est le suivant :
Each implementation of a Processor must provide a public no-argument constructor to be used by tools to instantiate the processor. The tool infrastructure will interact with classes implementing this interface as follows:
If an existing Processor object is not being used, to create an instance of a processor the tool calls the no-arg constructor of the processor class.
Next, the tool calls the init method with an appropriate ProcessingEnvironment.
Afterwards, the tool calls getSupportedAnnotationTypes, getSupportedOptions, and getSupportedSourceVersion. These methods are only called once per run, not on each round.
As appropriate, the tool calls the process method on the Processor object; a new Processor object is not created for each round.
JSR-269
En substance, il faut comprendre qu’une seule instance d’un annotation processor est créée par compilation. Il est donc tout à fait possible de traiter ce problème en exploitant l’aspect "stateful" des instances d’annotation processor (cela n’est certes pas très propre, mais c’est parfois indispensable).
Notez néanmoins que l’aspect stateful d’un annotation processor peut également vous jouer des tours en compilation incrémentale, mais nous sortons du cadre de cet atelier ;)
Important
|
Modifiez la classe HelloWorldProcessor de sorte que le message ne s’affiche plus qu’une seule fois par compilation
|
Important
|
Corrigez le doublon en utilisant l’API (astuce: regardez du côté de RoundEnvironment). |
Dans le répertoire exo3
, vous trouverez deux projets Maven et les classes de l’exo 2:
-
répertoire
processor
: le projetexo3-processor
produit unjar
qui contient la classefr.devoxx.niveau1.exo3.HelloWorldProcessor
. -
répertoire
subject
: le projetexo3-subject
contient la classefr.devoxx.niveau1.exo3.SomeDeprecatedClass
Important
|
Compilez le projet exo3-processor (pensez au install ) puis exo3-subject (compile suffit). Constatez qu’aucune ligne Hello world! ne s’affiche dans les traces Maven.
|
De la même manière qu’en utilisant javac
à la main, il faut ajouter une ligne de commande pour déclarer un annotation processor, avec Maven il faut ajouter quelques lignes dans le pom.xml
.
Le plugin Maven qui se charge de la compilation (et fait donc l’interface entre Maven et le compilateur) est le maven-compiler-plugin
.
Important
|
Trouvez comment déclarer le processor [WARNING] Hello world! |
L’activation des logs liés au processing d’annotations passait par des options de ligne de commande, tout comme la déclaration d’un processor. Avec Maven donc, pour activer ces logs, on utilisera aussi des options de configuration du maven-compiler-plugin.
Important
|
modifiez le pom.xml de exo3-subject de sorte que les logs du processing d’annotations s’affichent dans les logs du build Maven.
|
Le niveau de log utilisé dans l’implémentation Maven de HelloWorldProcessor
n’est pas le même que dans l’implémentation pour javac
.
Important
|
Pour comprendre pourquoi, faites un test avec les valeurs NOTE puis WARNING (et OTHER si vous y tenez) de l’enum javax.tools.Diagnostic.Kind .
|
Ce comportement est un "choix" du plugin maven-compiler-plugin
pour réduire la quantité de logs Maven (sic!) durant la phase de compilation.
Important
|
Trouvez l’option du plugin permet l’affichage des warnings de compilation dans Maven (documentation). |
Important
|
Tentez maintenant la compilation avec le niveau ERROR .
|
Constatez que vous avez maintenant dans vos mains le moyen de contrôler la compilation de vos classes.
Note
|
Par ailleurs, ce comportement permet de comprendre pourquoi on utilise une enum qui s’appelle Diagnostic.Kind et non quelque chose comme Level . En principe, on n’enregistre pas un log mais on transmet un diagnostic au compilateur (sous forme de message), en le qualifiant. Charge au compilateur ensuite de choisir ce qu’il en fait. Dans les faits, cela revient à afficher un log sauf si c’est le niveau ERROR auquel cas le compilateur arrête également la compilation.
|
L’obligation de déclarer explicitement son processor est un handicap au déploiement d’une solution basée sur un annotation processor.
Heureusement, la JSR-269 spécifie la présence d’un "discovery process". Celui de javac
est basé sur le ServiceLoader
de l’API Java.
La documentation de javac
indique:
Processors are located by means of service provider-configuration files named META-INF/services/javax.annotation.processing.Processor on the search path
Important
|
Ajoutez le fichier dans le répertoire Recompilez tout le projet ( [WARNING] Attention, il y a du code déprécié dans les sources de ce module ! |
Félicitations ! Il suffit maintenant d’avoir l’artefact fr.devoxx.2015.niveau1:exo4-processor1
comme dépendance avec le scope compile
pour bénéficier de ses avertissements (super utiles) à la compilation.
La création du fichier META-INF/services/javax.annotation.processing.Processor
et l’écriture de son contenu sont un exemple parfait de ce qui peut être automatisé avec le traitement d’annotations à la compilation.
Et pour preuve, c’est le but de la toute petite librairie AutoService
(3 classes).
Préparez votre totem, vous allez faire du traitement d’annotations sur un annotation processor.
Important
|
Ajoutez la dépendance [WARNING] True rewards await those who choose wisely. |
Fantastique ! Cela fonctionne ! Il est possible de faire du traitement d’annotations alors même que l’on code un processor, pas mal non ?
Vous aurez sûrement remarqué que la ligne produite par DeprecatedCodeWhistleblower
("[WARNING] Attention, il y a du code déprécié dans les sources de ce module !") est aussi présente lors de la compilation du module exo4-subject2
.
Comme ce processor utilise un "service provider-configuration files", cela signifie que le module exo4-subject2
déclare une dépendance vers le module exo4-processor1
.
Important
|
Vérifiez le pom.xml et constatez que ce n’est pas le cas.
|
En réalité, le module exo4-processor1
est une dépendance indirecte du module exo4-subject2
. En effet, celui-ci déclare une dépendance vers exo4-subject1
, qui déclare une dépendance à exo4-processor1
.
Du coup, exo4-processor1
est bien dans le classpath de exo4-subject2
et il se voit donc appliqué le processor de ce module.
Ce comportement est rarement souhaitable. Heureusement, il existe une option de la déclaration de dépendance Maven qui permet de le corriger, de faire en sorte d’avoir une dépendance de scope compile
mais que celle-ci ne puisse être tirée indirectement.
Important
|
Faites en sorte que la ligne de log du processor |
Si vous regardez le pom.xml
du module exo4-processor1
, vous constaterez qu’une option du compilateur a été ajoutée pour désactiver totalement le traitement d’annotations lors de la compilation de ce module.
Cette option est super-extrêmement-ultra-vachement importante si vous écrivez META-INF/services/javax.annotation.processing.Processor
à la main.
Important
|
Supprimez cette option, compilez le projet. Constatez que le build échoue avec le message suivant: [ERROR] Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: Provider fr.devoxx.niveau1.exo4.DeprecatedCodeWhistleblower not found |
Cette erreur signifie que Java n’a pas trouvé un processor alors que celui-ci est référencé dans un fichier META-INF/services/javax.annotation.processing.Processor
. Mais bon, forcément, il ne trouve pas un processor qu’il est censé compiler.
L’explication de ce comportement n’est pas trivial, accrochez-vous. Lors du build:
-
Maven copie les ressources dans le répertoire
exo4-processor1/target/classes
-
lors de la compilation, le
maven-compiler-plugin
spécifie àjavac
que le répertoireexo4-processor1/target/classes
fait partie de son classpath (un [ticket](https://jira.codehaus.org/browse/MCOMPILER-97) est ouvert sur le sujet depuis des années mais ce choix est requis pour le build incrémental) -
javac
constate donc la présence d’un fichierMETA-INF/services/javax.annotation.processing.Processor
dans le classpath et recherche donc le processor indiqué:DeprecatedCodeWhistleblower
-
ce processor n’existe pas (forcément, on est sur le point de le compiler) et
javac
lève une erreur et ne compile aucun fichier -
l’erreur ("error: Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: Provider fr.devoxx.niveau1.exo4.DeprecatedCodeWhistleblower not found") est remontée par le
maven-compiler-plugin
et le build échoue
Le workaround qui est "prescrit" pour ce problème est celui indiqué ci-dessus: désactiver le traitement d’annotations complètement lors de la compilation du processor.
Ce workaround est acceptable à la condition d’avoir isolé le processor dans son propre module (ce qui est sans doute une bonne pratique de toutes manières) et/ou que l’on a pas besoin d’annotation processing.
L’autre workaround consiste à utiliser l’annotation @AutoService
.
Attention, le build n’échoue que si Maven est exécuté avec Java 8. Avec Java 7 et 6, javac
ne rapporte aucune erreur (bug corrigé en 8) et ne compile toujours aucune classe. Donc voici la situation que l’on reprend au point 5:
-
l’erreur ("error: Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: Provider fr.devoxx.niveau1.exo4.DeprecatedCodeWhistleblower not found") est simplement ignorée par le
maven-compiler-plugin
(bug! gros bug!) qui considère que la compilation a réussi -
la compilation de
exo4-processor1
produit donc un jar qui ne contient queMETA-INF/services/javax.annotation.processing.Processor
-
ce jar est tiré par les modules
exo4-subject1
etexo4-subject2
, il y a donc dans le classpath un fichierMETA-INF/services/javax.annotation.processing.Processor
qui référence un processor inexistant,javac
lève une erreur et la compilation n’a pas lieu -
s’il n’y a pas de compilation, le message de
OverrideJones
ne peut pas s’afficher, pas de plus que celui deDeprecatedCodeWhistleblower
qui n’a pas été compilé
En conclusion, la présence d’un fichier META-INF/services/javax.annotation.processing.Processor
sans son processor peut sérieusement compromettre la compilation. Et encore plus celle d’un projet Maven dû à certains bugs du maven-compiler-plugin
si vous n’utilisez pas Java 8.