Einführung in Java-Annotationen
Was Java-Annotationen sind, wie sie Metadaten an Code anhängen und wo sie verarbeitet werden.
Eine Annotation ist ein Marker, den Sie an ein Java-Quellelement anhängen — eine Klasse, Methode, Feld, Parameter oder Typverwendung — der Metadaten hinzufügt. Die Annotation selbst tut nichts. Sie ist ein Label, das ein anderes Stück Code (der Compiler, ein Framework, eine IDE, ein Build-Tool) später liest und darauf reagiert. @Override auf einer Methode sagt dem Compiler: „Ich überschreibe eine Superklassenmethode; bitte beschwere dich, wenn das nicht der Fall ist." @Test auf einer Methode sagt JUnit: „Dies ist ein Test." @Entity auf einer Klasse sagt JPA: „Mappe diese in eine Datenbanktabelle." In jedem Fall trägt die Annotation nur Informationen; das Verhalten steckt im Prozessor, der sie liest.
Sie haben im Laufe dieses Buches bereits Annotationen gesehen — @Override auf überschriebenen Methoden, @FunctionalInterface auf Interfaces mit nur einer Methode, @SuppressWarnings zum Unterdrücken von Compilerwarnungen. Dieser Teil des Buches befasst sich mit dem System dahinter: was eine Annotation ist, wann sie verfügbar ist (nur zur Kompilierzeit, in der Klassendatei oder zur Laufzeit), wie man eigene schreibt und wie Prozessoren sie verarbeiten.
Die Struktur einer Annotation
Syntaktisch ist eine Annotation das @-Symbol gefolgt vom Annotationsnamen, unmittelbar vor dem annotierten Element:
@Override
public String toString() { ... }
@Deprecated
public void oldApi() { ... }
@SuppressWarnings("unchecked")
List<String> list = (List<String>) raw;Manche Annotationen nehmen Elemente entgegen (ihre Version von Feldern). Die Elementwerte stehen in Klammern:
@SuppressWarnings("unchecked") // one element, value "unchecked"
@SuppressWarnings({"unchecked", "rawtypes"}) // array of strings
@RequestMapping(path = "/users", method = GET) // two named elementsWenn eine Annotation ein einzelnes Element namens value deklariert, kann der Name weggelassen werden — @SuppressWarnings("unchecked") ist eine Kurzform für @SuppressWarnings(value = "unchecked").
Was Annotationen nicht sind
Drei Verneinungen, die die häufigsten Missverständnisse verhindern:
- Annotationen führen keinen Code aus. Das Schreiben von
@Cachedneben einer Methode bewirkt kein Caching. Etwas anderes muss nach@Cachedsuchen und das Caching-Verhalten hinzufügen. Die Annotation ist ein Flag, keine Funktion. - Annotationen sind keine Kommentare. Kommentare verschwinden bei der Kompilierung; Annotationen sind erstklassige Sprachkonstrukte. Sie nehmen am Typsystem teil, können in der Klassendatei verbleiben und zur Laufzeit per Reflection ausgelesen werden.
- Annotationen sind kein Ersatz für klaren Code. Ein langer Stapel von Annotationen über einer Klasse ist Informationsdichte, aber nicht immer Klarheit. Frameworks, die stark auf Annotationen setzen (Spring, JPA, JAX-RS), bezahlen den Komfort mit einer Lernkurve und Laufzeitkosten.
Wann die Annotation verfügbar ist
Jede Annotation hat eine Aufbewahrungsrichtlinie (Retention Policy), die bestimmt, wie lange die Metadaten erhalten bleiben:
SOURCE— der Compiler liest sie und verwirft sie dann.@Overrideund@SuppressWarningsfunktionieren so; der Bytecode enthält keine Aufzeichnung davon.CLASS— die Annotation wird in die.class-Datei geschrieben, wird aber zur Laufzeit nicht von der JVM geladen. Dies ist der Standard. Tools, die Bytecode inspizieren (statische Analysetools, Post-Prozessoren), können sie lesen.RUNTIME— die Annotation bleibt vollständig erhalten; Reflection kann zur Laufzeit jede Klasse, Methode oder jedes Feld nach seinen Annotationen befragen. Frameworks wie Spring und Jackson verlassen sich darauf.
Die @Retention(...)-Meta-Annotation, die diese Richtlinie setzt, wird im Kapitel über Meta-Annotationen behandelt. Kurzfassung: Wählen Sie die Aufbewahrungsrichtlinie danach, wer die Annotation lesen muss — der Compiler, ein Build-Zeit-Tool oder Laufzeitcode.
Wo die Annotation stehen kann
Jede Annotation hat auch ein Ziel (Target) — die Menge der Programmelemente, die sie legal annotieren darf. Die häufigen Ziele:
TYPE— Klassen, Interfaces, Enums.METHOD— Methoden.FIELD— Felder.PARAMETER— Methodenparameter.CONSTRUCTOR— Konstruktoren.LOCAL_VARIABLE— lokale Variablendeklarationen.ANNOTATION_TYPE— andere Annotationsdeklarationen (Meta-Annotationen).PACKAGE— Pakete, überpackage-info.java.TYPE_USE— jede Verwendung eines Typs, einschließlich generischer Parameter und Casts (Java 8+).
Wenn Sie eine Annotation dort platzieren, wo ihr @Target es nicht erlaubt, lehnt der Compiler ab. @Override auf einer Klassendeklaration ist ein Kompilierfehler, weil @Override auf METHOD ausgerichtet ist.
Wer Annotationen liest
Drei Stellen verarbeiten Annotationsdaten:
- Der Compiler. Eingebaute Annotationen wie
@Override,@SafeVarargsund@FunctionalInterfacewerden vonjavacselbst geprüft. - Annotationsprozessoren. Erweiterbare Kompilierzeit-Tools, die während
javaclaufen. Sie können Annotationen auf den kompilierten Quellen lesen und als Reaktion darauf neue Quelldateien generieren. Lombok, Dagger, Hibernates statisches Metamodell und dasMicronaut-Framework funktionieren auf diese Weise. - Reflection zur Laufzeit.
Method.getAnnotations(),Class.getAnnotation(...)usw. geben die Annotationsinstanzen für alle Elemente mitRUNTIME-Aufbewahrung zurück. So entscheidet Spring, was injiziert werden soll, wie JUnit Ihre Tests findet und wie Jackson JSON abbildet.
Die ersten beiden benötigen keine Unterstützung der virtuellen Maschine über das hinaus, was javac bereitstellt. Das Dritte erfordert, dass die Annotation in die Klassendatei geschrieben und von der Laufzeitumgebung geladen wird.
Ein praktisches Beispiel: Annotationen auf der eigenen Klasse inspizieren
Der Zweck dieses Beispiels ist zu zeigen, dass @Override, @Deprecated und @SuppressWarnings im Quellcode identisch aussehen, sich aber nach der Kompilierung unterschiedlich verhalten. Das Programm deklariert eine Klasse mit mehreren Annotationen und fragt dann per Reflection, was tatsächlich sichtbar ist.
Was aus dem Lauf zu entnehmen ist:
- Die Schleife auf Klassenebene sah
@DeprecatedaufGreeter, aber sonst nichts —@DeprecatedhatRUNTIME-Aufbewahrung.@Overrideund@SuppressWarningssindSOURCE, sodass der Compiler sie entfernte, bevor die Klassendatei geschrieben wurde, und Reflection sie nicht wiederherstellen kann. - Die methodenbezogene Schleife gab nur
@DeprecatedbeioldHelloaus. ObwohltoStringals@Overrideundcastals@SuppressWarnings("unchecked")deklariert waren, gelangte keine dieser Annotationen in die Klassendatei. Die Informationen existierten in dem Moment, in demjavaclief — so funktionierte die Override-Prüfung — und wurden dann verworfen. - Die Retention-Prüfung machte das deutlich:
@Overrideund@SuppressWarningstragen@Retention(SOURCE)in ihren eigenen Deklarationen, während@Deprecated@Retention(RUNTIME)trägt. Die Aufbewahrung ist eine Eigenschaft des Annotationstyps, nicht der Art, wie er verwendet wird. - Das Lesen von
@DeprecatedaufGreeterübercls.getAnnotation(Deprecated.class)gab einen Proxy zurück, dessen Elementmethoden (since(),forRemoval()) die im Quellcode geschriebenen Werte zurückgaben. Das ist die Laufzeitschnittstelle zu Annotationsmetadaten: eine Instanz, deren Elemente Accessormethoden sind. - Die Schlussfolgerung für die Wahl der Aufbewahrung: Wenn der einzige Verbraucher
javacist (Override-Prüfungen, Warnungsunterdrückung), verwenden SieSOURCE. Wenn ein Framework die Annotation während der Programmausführung lesen muss (DI, ORM, JSON-Bindung), verwenden SieRUNTIME. Das Kapitel über Meta-Annotationen erklärt, wie Sie das für Ihre eigenen Annotationstypen deklarieren.
Was in diesem Teil kommt
Die verbleibenden Kapitel dieses Teils führen Sie durch:
- Die häufigsten eingebauten Annotationen in
java.lang—@Override,@Deprecated,@SuppressWarnings,@SafeVarargs,@FunctionalInterface. - Die Meta-Annotationen, die Ihre eigenen konfigurieren —
@Retention,@Target,@Documented,@Inherited,@Repeatable. - Das Schreiben eigener Annotationstypen und deren Auslesen per Reflection.
- Die Kompilierzeit-API für die Annotationsverarbeitung, die Frameworks zum Generieren von Code nutzen.
Der Bogen verläuft von „welche Annotationen die Sprache bereitstellt" zu „welche Annotationen man darauf aufbauen kann".