Date post: | 15-Apr-2017 |
Category: |
Software |
Upload: | arnaud-tournier |
View: | 268 times |
Download: | 2 times |
PLUGGABLE ANNOTATIONPROCESSING APIJUG TOULOUSE - 2015 - LTE CONSULTING
ARNAUD TOURNIERArchiDév passionné chez LTE Consulting
Speaker Devoxx, GWT.create, Paris/Toulouse JUG, etc...
@ltearno www.lteconsulting.fr
Développement - Formation - Conseil
Full stack (x86_64 to JavaScript) !
AVANT TOUTCette présentation est disponible sur
github.com/ltearno/annotation-processing
Des hyper-liens sont présents
JSR 269 ???
PLUGGABLE ANNOTATIONPROCESSING API...
La JSR 269 - Pluggable Annotation Processing API permetd'exploiter les annotations présentes dans le code en
s'insérant dans le processus de compilation, et de générer denouvelles sources.
Traitement des annotations à la compilation,Génération de code,Génération de fichiers de configuration,Documentation, Cartographie,Vérifications, Build breakers,etc...
On ne modifie pas les sources existants !
AVANTAGES
Le code généré est visible.
Pas de traitement au runtime donc pas d'impact sur lesperformances.
Pas d'instrumentation du byte-code, donc plus simple.
BRÈVE HISTOIRE DUTRAITEMENT DES
ANNOTATIONS
COMMENTAIRES JAVADOCXDoclet (2002)
/***** Account entity bean** @ejb.bean* name="bank/Account"* jndi-name="ejb/bank/Account"* primkey-field="id"* schema = "Customers"* ...*/public class MonBean { ... }
APT, retiré officiellement avec Java 7
car .Annotation Processing Tool
non extensible à Java > 5
Outil lancé en dehors de la compilation.
L'API Mirror utilise les packages com.sun.mirror.
PLUGGABLE ANNOTATION PROCESSING APIDepuis 2006 (Java 6) la , créé par Joe Darcy.JSR-269
Intégré à la compilation javac.
PRINCIPE
On fournit un processeur d'annotation,Le compilateur gère des rounds de processing,A chaque round, les nouveaux sources sont traités (phasesParse et Enter),Les processeurs sont choisis et reçoivent l'AST des classestraitées,Les processeurs peuvent générer de nouveaux fichiers(sources, classes et resources) qui seront parsés et traités auround suivant.
UTILISATION
CRÉATION DE L'ANNOTATIONimport java.lang.annotation.*;
// package, class, method, ...@Target( value = { ElementType.METHOD } )@Retention( RetentionPolicy.SOURCE )public @interface MonAnnotation{ ...}
LE PROCESSEUR@SupportedAnnotationTypes( value= { "fr.lteconsulting.MonAnnotation" } )@SupportedSourceVersion( SourceVersion.RELEASE_6 )public class MonAnnotationProcessor extends AbstractProcessor { @Override public boolean process( Set<?> extends TypeElement> annotations, RoundEnvironment roundEnv ) { Types typeUtils = processingEnv.getTypeUtils(); Elements elementUtils = processingEnv.getElementUtils(); Messager messager = processingEnv.getMessager();
for ( TypeElement element : roundEnv.getElementsAnnotatedWith(MonAnnotation processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, element.getQualifiedName()); return true; }}
LE FICHIER SPIPour packager et activer un processeur, le plus simple est
d'utiliser SPI :
Le fichierMETA-INF/services/javax.annotation.processing.Processor
contient la liste des processeurs :
fr.lteconsulting.MyAnnotationProcessor
Packager le tout dans un jar
UN BOUT DE CODEDans un autre projet on peut utiliser l'annotation et le
processeur.
class UneClasse { @MonAnnotation void uneMethode() { ... }}
PACKAGING ET COMPILATIONLe processeur et le fichier SPI sont dans un jar.
Ce jar est dans le class path au moment de la compilation deUneClasse.java.
C'est tout !
SORTIE DE NOTRE EXEMPLEAt line 90 of UneClasse.java :fr.lteconsulting.UneClasse.uneMethode()
FONCTIONNEMENT
A chaque round, le processeur doit traiter les classesgénérées au round précédent.
S'il est appelé au premier round, il le sera pour les autres,jusqu'au dernier round (même si aucune annotation n'est
présente pour lui).
DÉCOUVERTE DES PROCESSEURSLes proceseurs sont découverts par le compilateur.
JavaCompiler fournit des options pour contrôler l'ensembledes processeurs disponibles :
une liste prédéfinie,un chemin de recherche,utiliser SPI.
CHOIX DU PROCESSEURAppel des processeurs en fonction :
des annotations présentes dans les classes traitées,les annotations supportées par les processeurs,le fait qu'un processeur ait claimé une annotation.
CYCLE DE VIE DU PROCESSEURLe compilateur instancie le processeur,Appelle init avec un ProcessingEnvironment,Appelle getSupportedAnnotationTypes,getSupportedOptions etgetSupportedSourceVersion,Et appelle process à chaque round.
A CHAQUE ROUNDjavac calcule l'ensemble des annotations sur les classes encours,si au moins une annotation est présente, au fur et à mesureque les processeurs les claime, elles sont retirées desannotations non matchées.quand l'ensemble est vide ou qu'il n'y a plus de processeurcandidat, le round est fini.si aucune annotation n'est présente, seuls les processeursuniversels ("*") sont appelés, et reçoivent un ensemble videde classes à traiter.
PRÉCAUTIONS !Un processeur ne doit pas dépendre d'un autre,Idempotent,Commutatif.
L'INTERFACE PROCESSORjavax.annotation.processing.Processor
interface Processor { void init( ProcessingEnvironment processingEnv );
Set<String> getSupportedAnnotationTypes();
boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv );}
PROCESSINGENVIRONMENTjavax.annotation.processing.ProcessingEnvironment
// UtilitairesElements getElementUtils();Types getTypeUtils();Locale getLocale();Map<String, String> getOptions();SourceVersion getSourceVersion();
// Création de fichiersFiler getFiler();
// Affichage utilisateurMessager getMessager();
// Autres classes :// ElementFilter, AbstractVisitors...
GETSUPPORTEDANNOTATIONTYPES*fr.lteconsulting.annotations.*fr.lteconsulting.annotations.MonAnnotation
LA MÉTHODE PROCESSboolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
On reçoit l'ensemble des annotations à traiter,On retourne true pour claimer les annotations etempêcher les autres processeurs de les traiter.
ROUNDENVIRONMENTjavax.annotation.processing.RoundEnvironment
Liste des classes dans le round :
Set<? extends Element> getRootElements()
Liste des éléments annotés :
Set<? extends Element> getElementsAnnotatedWith(TypeElement a)Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a)
Etat du round :
// si donne 'true', il ne faut rien générerboolean isProcessingOver()
ELEMENTjavax.lang.model.element.Element
Représente un package, une classe, une méthode, ...
Pour parcourir les données d'un élément, il faut soit appelergetKind() soit utiliser un visiteur.
Ne pas utiliser instanceof !
Petit exercice : POURQUOI ?
ELEMENT// Visiter l'élément :<R,P> R accept(ElementVisitor<R,P> v, P p);
// Obtenir le type :TypeMirror asType();
// Demander la sorte :ElementKind getKind();
// Demander les annotations présentes sur l'élément :List<? extends AnnotationMirror> getAnnotationMirrors();<A extends Annotation> A getAnnotation(Class<A> annotationType);
// Autres :// getModifiers(); getSimpleName(); getEnclosingElement(); getEnclosedElements();
LES SORTES D'ELEMENTElementKind
annotation, class, constructeur, enum, une constante enum,parametre d'exception, champ, initializeur d'instance, interface,
variable locale, méthode, package, paramètre, variable deresource, initializeur statique, paramètre de type, autres (futur).
LE FILERjavax.annotation.processing.Filer
CRÉER UN NOUVEAU SOURCE JAVAFiler filer = processingEnv.getFiler();JavaFileObject jfo = filer.createSourceFile( classElement.getQualifiedName() + "Info");PrintWriter pw = new PrintWriter( jfo );...
CRÉER UNE NOUVELLE RESSOURCEFiler filer = processingEnv.getFiler();try { PrintWriter pw = new PrintWriter(filer.createResource( StandardLocation.SOURCE_OUTPUT, "fr.lteconsulting", "Todo.txt") .openOutputStream()); pw.println("Quelque chose"); pw.close();} catch (IOException ioe) { messager.printMessage(Kind.ERROR, ioe.getMessage());}
CRÉER UN NOUVEAU FICHIER .CLASSFiler filer = processingEnv.getFiler();JavaFileObject jfo = filer.createClassFile( classElement.getQualifiedName() + "Info");PrintWriter pw = new PrintWriter( jfo );...
UTILISATION DE TEMPLATES !Ne générer que le minimum de code !Velocity, ...Java Poet, ...
LE MESSAGERmessager.printMessage( Kind.ERROR, "Cannot find an ID field !" );
// sortie :error: At Book.java, line 12 : Cannot find an ID field !1 error
LE MESSAGER DANS ECLIPSE
La même erreur dans Eclipse
LA COMPILATION JAVA
3 PHASES
Plan d'exécution de javacParse et EnterAnnotation ProcessingAnalyse et Generate
openjdk.java.net/groups/compiler/doc/compilation-overview
JAVACAction Paramètres
Générationdes sources
-s répertoire
Désigner unprocesseur
-processorfr.lteconsulting.MyAnnotationProcessor,autre...
Spécifier unchemin derecherche
-processorPath le_chemin
Passer desoptions
-Acle=valeur
JAVAC (SUITE)Action Paramètres
Désactiver l'AP -proc:none
Seulement l'AP -proc:only
Chemin de recherche dessources
-sourcePath
Désactiver la génération des.class implicite
-implicit:none
Répertoire de sortie des.class
-d
Affichage debug -XprintRounds -XprintProcessorInfo
ATTENTION : le warning si on ne met pas -implicit:none
docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching
INTÉGRATION AVECECLIPSE
CONFIGURATIONEclipse utilise son propre compilateur, JDT.
Configuration possible du projet par m2e
ATTENTIONDans Eclipse, si le projet contenant le processeur est ouvert,
l'annotation processing est désactivé.
TESTS UNITAIRES
EXEMPLEJavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// JDK Sun : task peut être casté en com.sun.source.util.JavacTaskCompilationTask task = compiler.getTask( null, // Default log (stderr) fileManager, // (InMemory)JavaFileManager diagnosticCollector, // DiagnosticCollector options, // Set<String> new HashSet<String>(), // Classes pour annotation processing sources); // Iterable<? extends JavaFileObject>
// Force les processeurstask.setProcessors(processors);
boolean successful = task.call(); // succès ou échecdiagnosticCollector.getDiagnostics(); // logs structurés de compilationfileManager.getOutputFiles(); // fichiers de sortie
COMPILE-TESTINGBibliothèque de test développée par Google, pour aider le
développement de et Auto Dagger
Test positif
assert_().about(javaSource()) .that(JavaFileObjects.forResource("HelloWorld.java")) .processedWith(new MyAnnotationProcessor()) .compilesWithoutError() .and() .generatesSources(JavaFileObjects.forResource("GeneratedHelloWorld.java"
et négatif
JavaFileObject fileObject = JavaFileObjects.forResource("HelloWorld.java"assert_().about(javaSource()) .that(fileObject) .processedWith(new NoHelloWorld()) .failsToCompile() .withErrorContaining("No types named HelloWorld!") .in(fileObject) .onLine(23) .atColumn(5);
DÉMO TIME !
LIMITATIONS
Pas d'accès à l'AST complet (corps des méthodes)Pas possible de modifier des classes existantesCertains bug ne permettent pas de traiter correctement lesgénériquesJava et les IDE ne surveillent que les éléments annotéspour redéclencher le processing (parfois problématiquelorsque l'on a des dépendances complexes -> Alt+F5 !)
HACKING AU DELÀ : de bons hacks pour accéder à l'implémentation
(javac de sun et jdt) et modifier l'ASTImmutables : quelques workarounds captant lesimplementations JDK / JDT pour gérer les génériquesExplications techniques générales dans
Lombok
The Hacker's guideto JavaC
EXEMPLES DEBIBLIOTHÈQUES
JPA meta-model generation (JSR-317),Dagger,Google Auto,Immutables,Hexa Binding,Lombok,GWT (RequestFactory)
LIENS EN VRAC
, , , , , , ,
, , , ,
, , , ,
Hibernate Validation JM Doudoux Lombok How Lombokworks ? Lombok encore... Hacking JavaC HexaBinding
Coders Breakfast Prez d'Angelika Langer Dr. Macphail'strance Créez et utilisz vos annotations Histoire des
annotation processing 4 vidéos sur Parleys Utilisation de laJSR-269 chez Les Furets Save method parameter names Au
JUG Paris par Olivier Croisier...
ET VOILÀ !
MERCI !Rendez-vous sur
github.com/ltearno/annotation-processing
Twitter : @ltearno
LinkedIn : fr.linkedin.com/in/lteconsulting
LTE Consulting