1. Einige grundlegende Konzepte
Bevor wir beginnen, müssen wir eine wichtige Sache erklären: Wir diskutieren nicht die Annotationen, die zur Laufzeit durch Reflexionsmechanismen verarbeitet werden, sondern die Anmerkungen, die zur Kompilierungszeit verarbeitet werden.
Was ist der Unterschied zwischen Kompilierungszeitanmerkungen und Anmerkungen zur Laufzeit? Tatsächlich ist es nicht groß, hauptsächlich aufgrund von Leistungsproblemen. Laufzeitanmerkungen beruhen hauptsächlich ausschließlich auf Reflexion, und die Effizienz der Reflexion ist langsamer als die der einheimischen, sodass auf Maschinen mit weniger Gedächtnis und schlechter CPU eine gewisse Verzögerung von Maschinen vorhanden ist. Die Annotation wird dieses Problem jedoch überhaupt nicht haben, da in unserem Kompilierungsprozess (Java->-Klasse) einige Annotationsmarken verwendet werden, um einige Klassen oder Dateien dynamisch zu generieren, sodass sie nichts mit unserem APK-Betrieb zu tun hat, sodass natürlich kein Leistungsproblem vorhanden ist. Wenn ein berühmteres Open-Source-Projekt die Annotationsfunktion verwendet, verwendet es normalerweise eine Annotation für Kompilierzeit.
Annotation -Prozessor ist ein Tool, das mit Javac geliefert wird und zum Scannen und Verfahren von Annotationsinformationen während der Kompilierungsperiode verwendet wird. Sie können Ihren eigenen Annotationsprozessor für bestimmte Anmerkungen registrieren. Ich gehe davon aus, dass Sie bereits verstehen, was Anmerkungen sind und wie Sie sie anpassen können. Wenn Sie die Anmerkungen noch nicht verstanden haben, können Sie die offizielle Dokumentation überprüfen. Annotationsprozessoren existierten bereits in Java 5, aber es gab keine API bis Java 6 (veröffentlicht im Dezember 2006). Es dauerte eine Weile, bis Java -Benutzer die Leistung des Annotationsprozessors erkannten. Daher ist es in den letzten Jahren nur populär geworden.
Ein Prozessor mit einer bestimmten Annotation nimmt den Java -Quellcode (oder kompilierte Bytecode) als Eingabe an und generiert dann einige Dateien (normalerweise .java -Dateien) als Ausgabe. Was bedeutet das? Sie können Java -Code generieren! Diese Java -Codes befinden sich in der generierten .java -Datei. Daher können Sie die vorhandene Java -Klasse nicht ändern, z. B. das Hinzufügen einer Methode. Diese generierten Java -Dateien werden von Javac wie anderer manuell geschriebener Java -Quellcode kompiliert.
Die Annotationsverarbeitung wird in der Kompilierungsphase ausgeführt. Sein Prinzip ist es, den Java -Quellcode zu lesen, die Anmerkungen zu analysieren und dann einen neuen Java -Code zu generieren. Der neu generierte Java -Code wird schließlich in Java -Bytecode zusammengestellt. Der Annotationsprozessor kann die Lese -Java -Klasse nicht ändern, z. B. das Hinzufügen oder Löschen von Java -Methoden.
2.. AbstractProcessor
Schauen wir uns die API des Prozessors an. Alle Prozessoren erben AbstractProcessor, wie unten gezeigt:
Paket com.example; import java.util.linkedhashset; import java.util.set; import javax.annotation.processing.abstractProcessor; import javax.annotation javax.annotation.processing.supportedannotationTypes; import javax.annotation Typelement> Ankündigungen, RoundEnvironment Env) {return false; } @Override public set <string> getSupportedAnnotationTypes () {set <string> Annotataions = new LinkedHashSet <string> (); Annotataions.Add ("com.example.myannotation"); Rückkehr Annotataionen; } @Override public SourceVersion getSupportedSourceVersion () {return SourceVersion.latestSupported (); } @Override public synchronisierte void init (processing -umwelt processionenv) {super.init (processionenv); }}Init (RecovingDeVironment ProcessingNV): Alle Annotationsprozessorklassen müssen über einen parameterlosen Konstruktor verfügen. Es gibt jedoch eine spezielle Methode init (), die vom Annotationsverarbeitungswerkzeug aufgerufen wird und die Verarbeitung als Parameter nimmt. Die Verarbeitungsumgebung bietet einige praktische Werkzeugkurse Elemente, Typen und Filer. Wir werden sie später verwenden.
process(Set<? extends TypeElement> annoations, RoundEnvironment env) : Dies ähnelt der main () -Methode jedes Prozessors. Sie können Scaning, Verarbeitung von Anmerkungen und Generieren von Java -Dateien in dieser Methode codieren und implementieren. Unter Verwendung des Parameters von Round -Environment können Sie Elemente abfragen, die mit einer bestimmten Annotation kommentiert sind (Originaltext: Sie können mit einer bestimmten Annotation nach Elementen abfragen). Wir werden die Details später sehen.
getSupportedAnnotationTypes(): In dieser Methode müssen Sie angeben, welche Anmerkungen vom Annotationsprozessor registriert werden sollten. Beachten Sie, dass der Rückgabewert eine Zeichenfolgensammlung ist, die den vollständigen Namen des Annotationstyps enthält, den Ihr Annotationsprozessor verarbeiten möchte. Mit anderen Worten, Sie definieren hier, welche Anmerkungen Ihr Annotationsprozessor verarbeiten wird.
getSupportedSourceVersion() : Wird verwendet, um die von Ihnen verwendete Java -Version anzugeben. Normalerweise sollten Sie SourceVersion.latestSupported() zurückgeben. Wenn Sie jedoch genügend Grund haben, sich an Java 6 zu halten, können Sie auch SourceVersion.RELEASE_6 zurückgeben. Ich empfehle die Verwendung von SourceVersion.latestSupported() . In Java 7 können Sie auch Anmerkungen verwenden, um das Umschreiben getSupportedAnnotationTypes() zu ersetzen und wie unten gezeigt: getSupportedSourceVersion() :
@SupportedSourceVersion (value = sourceVesion.release_7) @SupporteedannotationTypes ({// set von vollem Qulified Annotation Type "com.example.myannotation", "com.example.anotherannotation"}) öffentlich -proprozessor -standabscessor -{@override -öffentlich -booleaner -öffentlich -boolean -Prozess (Set concessRends, {| RoundEnvironment env) {return false; } @Override public synchronisierte void init (processing -umwelt processionenv) {super.init (processionenv); }} Aufgrund von Kompatibilitätsproblemen, insbesondere für Android, empfehle ich getSupportedAnnotationTypes() neu zu schreiben und getSupportedSourceVersion() zu verwenden, anstatt @SupportedAnnotationTypes und @SupportedSourceVersion zu verwenden.
Das nächste, was Sie wissen müssen, ist: Der Annotationsprozessor läuft in seiner eigenen JVM. Ja, Sie haben das Recht gelesen. Javac startet eine vollständige Java -virtuelle Maschine, um den Annotationsprozessor auszuführen. Was bedeutet das? Sie können alles verwenden, was Sie in einem normalen Java -Programm verwenden. Mit Guave! Sie können Abhängigkeitsinjektionsinstrumente wie Dolch oder eine andere Klassenbibliothek verwenden, die Sie verwenden möchten. Vergessen Sie jedoch nicht, dass Sie auch dann wie bei der Entwicklung anderer Java -Programme darauf achten sollten, effiziente Algorithmen und Designmuster zu verwenden, auch wenn es sich nur um einen winzigen Prozessor handelt.
3. Registrieren Sie Ihren Prozessor
Sie könnten fragen: "Wie kann ich meinen Annotationsprozessor bei Javac registrieren?". Sie müssen eine .jar -Datei bereitstellen. Genau wie bei anderen .Jar -Dateien verpacken Sie Ihren bereits kompilierten Annotationsprozessor in diese Datei. Und in Ihrer .jar-Datei müssen Sie eine spezielle Datei javax.annotation.processing.processor in das Meta-inf/dienste-Verzeichnis verpacken. So sieht Ihre .Jar -Dateiverzeichnisstruktur so aus:
MyProcess.jar -com -example -myprocess.class -meta -inf -services -javax.annotation.processing.processor
Der Inhalt der Datei javax.annotation.processing.Processor ist eine Liste, und jede Zeile ist der vollständige Name eines Annotationsprozessors. Zum Beispiel:
com.example.myProcess
com.example.AnotherProcess
4. Beispiel: Fabrikmodell
Das Problem, das wir lösen möchten, ist: Wir möchten ein Pizza -Geschäft implementieren, das Kunden zwei Pizza (Margherita und Calzone) sowie das Dessert Tiramisu (Tiramisu) zur Verfügung stellt.
öffentliche Schnittstelle Mahlzeit {public float getPrice ();} öffentliche Klasse Margheritapizza implementiert Mahlzeit {@Override public float getPrice () {return 6.0f; }} öffentliche Klasse Calzonepizza implementiert Mahlzeit {@Override public float getPrice () {return 8.5f; }} öffentliche Klasse Tiramisu implementiert Mahlzeit {@Override public float GetPrice () {return 4.5f; }} public class pizzastore {public mahlzeit order (string mahlname) {if (null == mesimname) {werfen neuer illegalArgumentException ("Name der Mahlzeit ist null!"); } if ("margherita" .equals (mahlzeitname)) {return New Margheritapizza (); } if ("calzone" .Equals (mahlzeitname)) {return New Calzonepizza (); } if ("Tiramisu" .Eequals (Mahlzeitname)) {return New Tiramisu (); } neue illegalArgumentException ("Unbekannte Mahlzeit" + Mahlzeitname + "'"); } private statische String -ReadConsole () {Scanner scanner = neuer Scanner (System.in); String mahlzeit = scanner.nextline (); scanner.close (); Rückmahlzeit; } public static void main (String [] args) {System.out.println ("Willkommen zum Pizza Store"); Pizzastore Pizzastore = neuer Pizzastore (); Mahlzeitenmahlzeit = Pizzastore.order (Readconsole ()); System.out.println ("bill: $" + meea.getPrice ()); }}Wie Sie sehen können, haben wir in der order () -Methode viele IN -Anweisungen für Bedingungsbeurteilungen. Und wenn wir eine neue Pizza hinzufügen, müssen wir ein neues, wenn auch bedingter Urteilsvermögen hinzufügen. Warten Sie jedoch, wenn Sie den Annotationsprozessor und den Fabrikmodus verwenden, können wir einen Annotationsprozessor bei Anweisungen generieren lassen. Auf diese Weise sieht der Code, den wir wollen, so aus:
public class pizzastore {private mealfactory fabry = new meastfactory (); öffentliche Mahlzeit (String MealName) {return factory.create (Mahlzeitname); } private statische String -ReadConsole () {Scanner scanner = neuer Scanner (System.in); String mahlzeit = scanner.nextline (); scanner.close (); Rückmahlzeit; } public static void main (String [] args) {System.out.println ("Willkommen zum Pizza Store"); Pizzastore Pizzastore = neuer Pizzastore (); Mahlzeitenmahlzeit = Pizzastore.order (Readconsole ()); System.out.println ("bill: $" + meea.getPrice ()); }} public class meakfactory {public mahlzeit create (String id) {if (id == null) {neue illegalArgumentException ("id is null!"); } if ("calzone" .Equals (id)) {return New Calzonepizza (); } if ("tiramisu" .Eequals (id)) {return New Tiramisu (); } if ("margherita" .equals (id)) {return New Margheritapizza (); } neue IllegalArgumentException ("Unbekannte id =" + id); }}5. @Factory Annotation
Können Sie erraten, dass wir vorhaben, den Annotationsprozessor zu verwenden, um die Mahlzeitenklasse zu generieren? Allgemeiner möchten wir eine Annotation und einen Prozessor zur Erzeugung von Fabrikklassen anbieten.
Schauen wir uns die @Factory Annotation an:
@Target (elementtype.type) @Retention (retentionPolicy.class) public @Interface Factory { / ** * Der Name der Fabrik * / class <?> Type (); / ** * Die Kennung zur Bestimmung des Elements sollte instanziiert werden */ String id ();}Die Idee lautet wie folgt: Wir kommentieren diese Lebensmittelklassen, verwenden Typ (), um anzugeben, zu welcher Fabrik diese Klasse gehört, und verwenden ID (), um den spezifischen Typ dieser Klasse anzugeben. Wenden wir die @Factory Annotation auf diese Klassen an:
@Factory (type = margheritapizza.class, id = "margherita") public class margheritapizza implementiert mahlzeit {@Override public float getPrice () {return 6.0f; }} @Factory (type = calzonepizza.class, id = "calzone") öffentliche Klasse CalzonePizza implementiert Mahlzeit {@Override public float getPrice () {return 8.5f; }} @Factory (type = tiramisu.class, id = "tiramisu") öffentliche Klasse Tiramisu implementiert Mahlzeit {@Override public float getPrice () {return 4.5f; }}Sie könnten fragen, können wir einfach die @Factory Annotation auf die Mahlzeitenschnittstelle anwenden? Die Antwort lautet Nein, weil die Anmerkung nicht geerbt werden kann. Das heißt, wenn Anmerkungen in der Klasse X vorhanden sind, erweitert die Klasse Y X, dann erben die Klasse Y die Anmerkungen zu Klasse X nicht. Bevor wir einen Prozessor schreiben, müssen wir einige Regeln klären:
Annotationsprozessor:
Public Class FactoryProcessor erweitert den AbstractProcessor {private Typen -Typutils; Private Elements Elementutils; privater Filer -Filer; Privat Messager Messager; private map <string, factoryGroupedClasses> factoryclasses = new LinkedHasMap <String, FactoryGroupedClasses> (); @Override public synchronisierte void init (processionenvironment processionenv) {super.init (processingEnv); typeUtils = processionenv.gettypeutils (); elementutils = processionenv.getElementutils (); filer = processionenv.getFiler (); Messager = processionenv.getMessager (); } @Override public boolean Process (set <? Erweitert Typelement> arg0, RoundEnvironment arg1) {... return false; } @Override public set <string> getSupportedAnnotationTypes () {set <string> Annotataions = new LinkedHashSet <string> (); Annotataions.Add (factory.class.getCanonicalName ()); Rückkehr Annotataionen; } @Override public SourceVersion getSupportedSourceVersion () {return SourceVersion.latestSupported (); }} In der getSupportedAnnotationTypes() -Methode geben wir an, dass die @Factory -Annotation von diesem Prozessor verarbeitet wird.
Zusammenfassen
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Referenzwert für das Studium oder die Arbeit eines jeden hat. Wenn Sie Fragen haben, können Sie eine Nachricht zur Kommunikation überlassen. Vielen Dank für Ihre Unterstützung bei Wulin.com.