1. Quelques concepts de base
Avant de commencer, nous devons déclarer une chose importante: nous ne discutons pas des annotations qui sont traitées par des mécanismes de réflexion lors de l'exécution, mais discutant des annotations qui sont traitées au moment de la compilation.
Quelle est la différence entre les annotations de temps de compilation et les annotations d'exécution? En fait, ce n'est pas grand, principalement en raison de problèmes de performances. Les annotations d'exécution reposent principalement entièrement sur la réflexion, et l'efficacité de la réflexion est plus lente que celle des indigènes, il y aura donc un peu de retard sur les machines avec moins de mémoire et un CPU pauvre. Cependant, l'annotation n'aura pas du tout ce problème, car dans notre processus de compilation (classe Java->), certaines marques d'annotation sont utilisées pour générer dynamiquement certaines classes ou fichiers, donc cela n'a rien à voir avec notre fonctionnement APK, il n'y a donc naturellement aucun problème de performance. Par conséquent, si un projet open source plus célèbre utilise une fonction d'annotation, il utilise généralement l'annotation en temps de compilation.
Le processeur d'annotation est un outil fourni avec Javac, qui est utilisé pour analyser et traiter les informations d'annotation pendant la période de compilation. Vous pouvez enregistrer votre propre processeur d'annotation pour certaines annotations. Ici, je suppose que vous comprenez déjà ce que sont les annotations et comment les personnaliser. Si vous n'avez pas encore compris les annotations, vous pouvez vérifier la documentation officielle. Les processeurs d'annotation existaient déjà dans Java 5, mais aucune API n'était disponible avant Java 6 (publiée en décembre 2006). Il a fallu un certain temps avant que les utilisateurs de Java ne réalisent la puissance du processeur d'annotation. Il n'est donc devenu populaire que ces dernières années.
Un processeur avec une annotation spécifique prend le code source Java (ou bytecode compilé) en entrée, puis génère certains fichiers (généralement des fichiers .java) en sortie. Qu'est-ce que cela signifie? Vous pouvez générer du code Java! Ces codes Java sont dans le fichier .java généré. Par conséquent, vous ne pouvez pas modifier la classe Java existante, comme l'ajout d'une méthode. Ces fichiers Java générés seront compilés par Javac comme d'autres code source Java écrits manuellement.
Le traitement d'annotation est exécuté au stade de compilation. Son principe est de lire le code source Java, d'analyser les annotations, puis de générer un nouveau code Java. Le code Java nouvellement généré est finalement compilé en Java Bytecode. Le processeur d'annotation ne peut pas modifier la classe Java Read, comme l'ajout ou la suppression des méthodes Java.
2. AbstractProcessor
Jetons un coup d'œil à l'API du processeur. Tous les processeurs héritent de l'abstractProcessor, comme indiqué ci-dessous:
package com.example; import java.util.linkedhashset; import java.util.set; import javax.annotation.processing.abstractProcess Javax.annotation.processing.SupporTedannotationTypes; Importer Javax.annotation.processing.SupporTedSourceversion; Importer javax.lang.model.sourceversion; Importer Javax.lang.model.element.typeelement; Classe publique Myprocessor Extend <? étend TypeElement> Annonces, Rountenirvironment Env) {return false; } @Override public set <string> getSupportEnannotationTypes () {set <string> annotaTaions = new LinkedHashSet <string> (); annotataions.add ("com.example.myannotation"); Retour annotates; } @Override public SourceVersion getSupporTedSourceVersion () {return SourceVersion.LaSestSupportted (); } @Override public synchronisé void init (traitement environment processenv) {super.init (processenv); }}INIT (ProcessenVirvironment Processinv): Toutes les classes de processeurs d'annotation doivent avoir un constructeur sans paramètre. Cependant, il existe une méthode spéciale init (), qui est appelée par l'outil de traitement de l'annotation, prenant le traitement de l'environnement en tant que paramètre. Le traitement de l'environnement fournit certains éléments, types et déclarants de classes d'outils pratiques. Nous les utiliserons plus tard.
process(Set<? extends TypeElement> annoations, RoundEnvironment env) : Ceci est similaire à la méthode principale () de chaque processeur. Vous pouvez encoder et implémenter la numérisation, le traitement des annotations et la génération de fichiers Java dans cette méthode. En utilisant le paramètre Rountenirvironment, vous pouvez interroger les éléments annotés avec une certaine annotation (texte d'origine: vous pouvez interroger pour les éléments annotés avec une certaine annotation). Nous verrons les détails plus tard.
getSupportedAnnotationTypes(): Dans cette méthode, vous devez spécifier quelles annotations doivent être enregistrées par le processeur d'annotation. Notez que sa valeur de retour est une collection de chaînes qui contient le nom complet du type d'annotation que votre processeur d'annotation souhaite traiter. En d'autres termes, vous définissez ici quelles annotations votre processeur d'annotation va gérer.
getSupportedSourceVersion() : utilisé pour spécifier la version Java que vous utilisez. Habituellement, vous devez retourner SourceVersion.latestSupported() . Cependant, si vous avez suffisamment de raisons de vous en tenir à Java 6, vous pouvez également retourner SourceVersion.RELEASE_6 . Je recommande d'utiliser SourceVersion.latestSupported() . Dans Java 7, vous pouvez également utiliser des annotations pour remplacer la réécriture getSupportedAnnotationTypes() et getSupportedSourceVersion() , comme indiqué ci-dessous:
@SupporTedSourCeversion (Value = SourceVersion.release_7) @SupporTedannotationTypes ({// Set of Full Qulified Annotation Names "Com.example.myannotation", "Com.example.anotherannotation"}) Classe publique MyProcessor TypeElt> AbstractProcessor {@Override Boolean Process (set <? Annonces, Roundenvironment Env) {return false; } @Override public synchronisé void init (traitement environment processenv) {super.init (processenv); }} En raison de problèmes de compatibilité, en particulier pour Android, je recommande de réécrire getSupportedAnnotationTypes() et getSupportedSourceVersion() au lieu d'utiliser @SupportedAnnotationTypes et @SupportedSourceVersion .
La prochaine chose que vous devez savoir est: le processeur d'annotation s'exécute dans son propre JVM. Oui, vous avez bien lu. Javac démarre une machine virtuelle Java complète pour exécuter le processeur d'annotation. qu'est-ce que cela signifie? Vous pouvez utiliser tout ce que vous utilisez dans un programme Java normal. Utilisation de la goyave! Vous pouvez utiliser des outils d'injection de dépendance tels que Dagger ou toute autre bibliothèque de classe que vous souhaitez utiliser. Mais n'oubliez pas que même si ce n'est qu'un petit processeur, vous devez faire attention à l'utilisation d'algorithmes et de modèles de conception efficaces, comme vous le faites pour développer d'autres programmes Java.
3. Enregistrez votre processeur
Vous pourriez demander "Comment enregistrer mon processeur d'annotation à Javac?". Vous devez fournir un fichier .jar. Tout comme les autres fichiers .jar, vous emballez votre processeur d'annotation déjà compilé dans ce fichier. Et, dans votre fichier .jar, vous devez emballer un fichier spécial javax.annotation.processing.processor dans le répertoire Meta-Inf / Services. Ainsi, votre structure de répertoire de fichiers .jar ressemble à ceci:
MyProcess.jar -com -example -myprocess.class -meta-inf -services -javax.annotation.processing.processor
Le contenu du fichier javax.annotation.processing.processor est une liste, et chaque ligne est le nom complet d'un processeur d'annotation. Par exemple:
com.example.myprocess
com.example.anotherprocess
4. Exemple: modèle d'usine
Le problème que nous voulons résoudre est: nous voulons mettre en œuvre un magasin de pizza, qui offre aux clients deux pizzas (Margherita et Calzone), ainsi que le dessert Tiramisu (Tiramissu).
Public Interface Meal {public float getPrice ();} classe publique Margheritapizza implémente le repas {@Override public float getPrice () {return 6.0f; }} classe publique CalzonePizza implémente la repas {@Override public float getPrice () {return 8.5f; }} classe publique Tiramissu implémente la repas {@Override public float getPrice () {return 4.5f; }} classe publique Pizzastore {Commande de repas public (String mealName) {if (null == mealName) {lancez new illégalargumentException ("le nom du repas est null!"); } if ("margherita" .equals (farine)) {return new margheritapizza (); } if ("calzone" .equals (farine)) {return new CalzonePizza (); } if ("tiramisu" .equals (farine)) {return new tiramisu (); } Jetez un nouveau IllégalArgumentException ("Inconnu Meal '" + farine + "'"); } private static String ReadConsole () {Scanner Scanner = new Scanner (System.in); String farine = Scanner.NextLine (); scanner.close (); Retour repas; } public static void main (String [] args) {System.out.println ("Welcome to Pizza Store"); Pizzastore pizzastore = new pizzastore (); Repas farine = pizzastore.order (readconsole ()); System.out.println ("Bill: $" + repas.GetPrice ()); }}Comme vous pouvez le voir, dans la méthode Order (), nous avons beaucoup de déclarations de jugement de condition IF. Et, si nous ajoutons une nouvelle pizza, nous devons ajouter un nouveau jugement conditionnel. Mais attendez, en utilisant le processeur d'annotation et le mode d'usine, nous pouvons faire en sorte qu'un processeur d'annotation génére ces instructions. De cette façon, le code que nous voulons ressemble à ceci:
classe publique Pizzastore {private farinefactory usine = new farinefactory (); Ordre des repas publics (chaîne de repas) {return factory.Create (farine); } private static String ReadConsole () {Scanner Scanner = new Scanner (System.in); String farine = Scanner.NextLine (); scanner.close (); Retour repas; } public static void main (String [] args) {System.out.println ("Welcome to Pizza Store"); Pizzastore pizzastore = new pizzastore (); Repas farine = pizzastore.order (readconsole ()); System.out.println ("Bill: $" + repas.GetPrice ()); }} public class faringfactory {public faring create (String id) {if (id == null) {lancez new illégalArgumentException ("id est null!"); } if ("calzone" .equals (id)) {return new CalzonePizza (); } if ("tiramisu" .equals (id)) {return new tiramisu (); } if ("margherita" .equals (id)) {return new margheritapizza (); } Jetez un nouveau IllégalArgumentException ("ID inconnu =" + id); }}5. @factory Annotation
Pouvez-vous deviner que nous prévoyons d'utiliser le processeur d'annotation pour générer la classe FalFactory. Plus généralement, nous voulons fournir une annotation et un processeur pour générer des cours d'usine.
Jetons un coup d'œil à l'annotation @factory:
@Target (elementType.Type) @retention (retentionPolicy.class) public @Interface Factory {/ ** * Le nom de l'usine * / class <?> Type (); / ** * L'identifiant pour déterminer quel élément doit être instancié * / String id ();}L'idée est la suivante: nous annotons ces classes de nourriture, utilisons type () pour indiquer à quelle usine appartient cette classe et utilisons id () pour indiquer le type spécifique de cette classe. Appliquons l'annotation @factory à ces classes:
@Factory (type = margheritapizza.class, id = "margherita") classe publique Margheritapizza implémente la repas {@Override public float getPrice () {return 6.0f; }} @ Factory (type = calzonepizza.class, id = "CalZone") Classe publique Calzonepizza implémente la repas {@Override public float getPrice () {return 8.5f; }} @ Factory (type = tiramisu.class, id = "tiramisu") classe publique Tiramissu implémente la repas {@Override public float getPrice () {return 4.5f; }}Vous pourriez demander, pouvons-nous simplement appliquer l'annotation @factory à l'interface des repas? La réponse est non, car l'annotation ne peut pas être héritée. Autrement dit, s'il y a des annotations sur la classe X, la classe Y étend X, alors la classe Y n'héritera pas des annotations de la classe X. Avant d'écrire un processeur, nous devons clarifier quelques règles:
Processeur d'annotation:
classe publique FactoryProcessor étend AbstractProcessor {types privés typeUtils; Éléments privés ElementUtils; filer de fileter privé; Messager privé Messager; Map privé <chaîne, factoryGroupEdClasses> factoryClasses = new LinkedHashMap <String, factoryGroupEdClasses> (); @Override public synchronisé void init (traitement environment processenv) {super.init (processingenv); typeUtils = processenv.getTypeutils (); elementUtils = processingenv.getElementUtils (); filer = processingenv.getFiler (); Messager = ProcessenV.GetMessager (); } @Override Public Boolean Process (set <? Étend TypeElement> Arg0, Roundenvironment Arg1) {... return false; } @Override public set <string> getSupportEnannotationTypes () {set <string> annotaTaions = new LinkedHashSet <string> (); annotataions.add (factory.class.getCanonicalName ()); Retour annotates; } @Override public SourceVersion getSupporTedSourceVersion () {return SourceVersion.LaSestSupportted (); }} Dans la méthode getSupportedAnnotationTypes() , nous spécifions que l'annotation @factory sera traitée par ce processeur.
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.