W3docs

Java Meta-Annotationen

Annotationen, die andere Annotationen in Java annotieren — @Retention, @Target, @Documented, @Inherited, @Repeatable.

Eine Meta-Annotation ist eine Annotation, die Sie auf eine Annotationsdeklaration setzen. Sie konfigurieren, wie sich diese Annotation verhält: wie lange sie erhalten bleibt, wo sie verwendet werden darf, ob Unterklassen sie erben und ob Sie sie mehr als einmal anwenden können. In java.lang.annotation gibt es fünf davon, und jeder Annotationstyp, den Sie schreiben, wird mindestens die ersten beiden verwenden.

Die fünf:

  • @Retention — steuert, ob die Annotation in .class-Dateien und zur Laufzeit erhalten bleibt.
  • @Target — schränkt ein, auf welche Arten von Programmelementen die Annotation angewendet werden kann.
  • @Documented — lässt die Annotation im generierten Javadoc erscheinen.
  • @Inherited — bewirkt, dass Unterklassen Annotationen auf Klassenebene von ihrer Oberklasse erben.
  • @Repeatable — erlaubt, dieselbe Annotation mehr als einmal auf dasselbe Element anzuwenden.

@Retention

@Retention wählt einen von drei RetentionPolicy-Werten:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)        // stripped by javac; never in bytecode
@interface SuppressEvenMoreWarnings { }

@Retention(RetentionPolicy.CLASS)         // in bytecode; not loaded by the VM (the default)
@interface BytecodeOnly { }

@Retention(RetentionPolicy.RUNTIME)       // in bytecode and accessible via reflection
@interface MyRuntimeMarker { }

Die richtige Wahl hängt vom Verbraucher ab:

  • Der Compiler ist der Verbraucher (Override-Prüfungen, Warnungsunterdrückung, Lint) → SOURCE.
  • Ein Bytecode-Verarbeitungswerkzeug, JIT-Optimierer oder Post-Compile-Analyser ist der Verbraucher → CLASS.
  • Ein Framework liest die Annotation zur Laufzeit über Reflection (DI, ORM, JSON-Binding) → RUNTIME.

RUNTIME ist die großzügigste Option — sie ist auch die kostspieligste, da jede Annotation, die erhalten bleibt, Bytes zur Klassendatei hinzufügt und beim Lookup einen kleinen Reflection-Overhead verursacht.

@Target

@Target schränkt ein, wo die Annotation platziert werden kann. Ihr Wert ist ein Array von ElementType-Konstanten:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@interface Audited { }                                   // only on methods or constructors

@Target(ElementType.TYPE)
@interface Entity { }                                    // only on classes/interfaces/enums

@Target({})
@interface CannotBeApplied { }                           // exists only as a type — can't be used to annotate anything

Die ElementType-Werte, denen Sie begegnen werden:

  • TYPE — Klasse, Interface, Enum, Annotation.
  • METHOD, CONSTRUCTOR, FIELD, PARAMETER, LOCAL_VARIABLE.
  • ANNOTATION_TYPE — für Meta-Annotationen wie die in diesem Kapitel.
  • PACKAGE — in package-info.java.
  • TYPE_USE (Java 8+) — jede Verwendung eines Typs, einschließlich Casts ((@NonNull String) o), extends-Klauseln, generische Argumente. Wird von Nullability-Checkern wie dem Checker Framework verwendet.
  • TYPE_PARAMETER (Java 8+) — nur bei <T extends ...>-Deklarationen.
  • MODULE (Java 9+) — in module-info.java.
  • RECORD_COMPONENT (Java 16+) — bei den Parametern eines Record-Headers.

Wenn Sie @Target ganz weglassen, kann die Annotation fast überall verwendet werden — nützlich für sehr allgemeine Marker, aber einschränkend für alles andere. Setzen Sie fast immer ein explizites @Target.

@Documented

Standardmäßig werden Annotationen nicht im Javadoc der Elemente angezeigt, die sie dekorieren. @Documented sorgt dafür, dass eine Annotation in die Dokumentation aufgenommen wird:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ApiNote {
  String value();
}

Wenn eine Methode @ApiNote("rate-limited to 5 rps") trägt, wird Javadoc diese Annotation in den gerenderten Docs anzeigen. Ohne @Documented existiert die Annotation zur Laufzeit, ist aber in der generierten Dokumentation unsichtbar. Fügen Sie @Documented zu allem hinzu, das Benutzer Ihrer Bibliothek sehen sollen.

@Inherited

@Inherited gilt nur für Annotationen, die auf TYPE (Klassen) abzielen. Es bedeutet: Wenn eine Klasse annotiert ist, werden ihre Unterklassen ebenfalls als annotiert behandelt. Reflections getAnnotation(...) wird die Oberklassen-Kette hinaufgehen und die Annotation finden.

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Auditable { }

@Auditable
class Account { }

class SavingsAccount extends Account { }                 // also "auditable" via inheritance

Einschränkungen:

  • Es wird nur die Oberklassen-Kette durchlaufen — Interfaces propagieren keine Annotationen, auch nicht mit @Inherited. class Foo implements Auditable { } bewirkt nicht, dass Foo ein @Auditable vom Interface erbt.
  • Es betrifft nur, wie Reflection Annotationen auf Klassen meldet. Methoden, Felder und Parameter erben niemals Annotationen von überschriebenen Mitgliedern.

Die meisten Frameworks bevorzugen heutzutage explizite, wiederholte Annotationen gegenüber Vererbung, weil die Regeln einfacher sind. Verwenden Sie @Inherited nur, wenn "alles, was eine Marker-Klasse erweitert, ebenfalls markiert ist" wirklich das ist, was Sie wollen.

@Repeatable

Vor Java 8 konnte man dieselbe Annotation nicht zweimal auf dasselbe Element anwenden. @Repeatable hebt diese Einschränkung auf, aber die Mechanik erfordert Sorgfalt. Sie deklarieren eine Container-Annotation, die ein Array der wiederholten Werte enthält, und verweisen mit @Repeatable auf den Container:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedules { Schedule[] value(); }            // the container

@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedule { String cron(); }                  // the repeated annotation

class Reports {
  @Schedule(cron = "0 9 * * MON")
  @Schedule(cron = "0 9 * * FRI")
  public void weekly() { /* ... */ }
}

Regeln:

  • Das value-Element des Containers muss ein Array des wiederholten Annotationstyps sein.
  • Der Container und die wiederholte Annotation sollten dieselbe Retention und mindestens dieselben Targets haben.
  • Wenn die wiederholte Annotation @Documented ist, muss der Container ebenfalls @Documented sein — und ebenso für @Inherited. Der Compiler lehnt einen Konflikt mit containing annotation interface ... is not @Documented ab. Halten Sie ihre Meta-Annotationen synchron.
  • Zur Laufzeit bündelt der Compiler wiederholte Verwendungen in eine einzige Container-Annotation. Reflection hat sowohl getAnnotation(Schedule.class) (gibt das einzelne Container-Element zurück, wenn es zwei gibt) als auch getAnnotationsByType(Schedule.class) (gibt das Array direkt zurück). Verwenden Sie die zweite Form bei @Repeatable-Annotationen.

Ein ausgearbeitetes Beispiel: alle fünf Meta-Annotationen auf einer echten Annotation

Das Beispiel erstellt ein kleines Annotationssystem von Grund auf: ein @Schedule, das RUNTIME-sichtbar, nur für Methoden, dokumentiert und wiederholbar ist; ein Marker @Module, der von Unterklassen geerbt wird. Die Main-Methode liest sie dann über Reflection.

java— editable, runs on the server

Was man aus der Ausführung mitnehmen sollte:

  • @Module wurde auf der Oberklasse ReportingBase deklariert, aber Reflection fand es auf WeeklyReports, weil die Annotation @Inherited trägt. Der Lookup durchläuft die Klassenhierarchie, bis er die Annotation findet oder keine Oberklassen mehr vorhanden sind.
  • Der Interface-Fall zeigte die Grenze der Vererbung: WithInterface implementiert AnnotatedInterface, das @Module hat, aber getAnnotation(Module.class) gab null zurück. @Inherited durchläuft nur extends, niemals implements. Dies verwirrt viele Einsteiger; wenn Sie Annotationssichtbarkeit über Interfaces hinweg benötigen, müssen Sie den Typbaum selbst durchlaufen.
  • runWeekly trug zwei @Schedule-Annotationen. getAnnotationsByType(Schedule.class) gab ein Array der Länge 2 zurück — die richtige Art, wiederholte Annotationen zu lesen. Der Container @Schedules ist für Benutzercode unsichtbar, wenn Sie bei getAnnotationsByType bleiben.
  • Der Fall mit einer einzigen @Schedule bei runDaily war symmetrisch: getAnnotation(Schedule.class) funktionierte, weil es keinen Container gab, und getAnnotationsByType gab ein Array der Länge 1 zurück. Beide Formen sind in Ordnung, wenn Sie die Multiplizität kennen.
  • Die Zeilen "repeated case via getAnnotation" enthüllten die Falle. Bei runWeekly gab getAnnotation(Schedule.class) null zurück — die tatsächliche Annotation in der Klassendatei ist ein synthetisierter @Schedules-Container, kein Schedule. Der Zugriff auf den Container über getAnnotation(Schedules.class) funktioniert. Die Regel: Für jede @Repeatable-Annotation immer getAnnotationsByType verwenden, damit die beiden Fälle (ein Vorkommen vs. viele) identisch aussehen.

Ihre Auswahl treffen

Wenn Sie eine neue Annotation schreiben, entscheiden Sie alle fünf auf einmal:

  1. Wer muss sie lesen? → @Retention.
  2. Wo darf sie erscheinen? → @Target.
  3. Sollen Benutzer sie im Javadoc sehen? → @Documented oder nicht.
  4. Sollen Unterklassen sie erben? → @Inherited für Marker auf Klassenebene wie @Auditable. Weglassen für Annotationen auf Methodenebene.
  5. Soll sie mehr als einmal angewendet werden? → @Repeatable wenn und nur wenn Sie wirklich Multiplizität benötigen.

Das Standard-Skelett für eine zur Laufzeit sichtbare Methoden-Annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@interface MyAnnotation { /* elements */ }

Das nächste Kapitel — Benutzerdefinierte Annotationen — verwendet genau dieses Muster, um eine von Grund auf zu erstellen und sie mit Reflection zu verwenden. Für die Annotationen, die das JDK mitliefert, siehe Eingebaute Annotationen; für die oben verwendete Reflection API siehe Annotationen mit Reflection lesen.

Übungen

Übung
Sie deklarieren `@Tagged` mit `@Inherited` und `@Target(ElementType.TYPE)`. `interface Marker {}` ist mit `@Tagged` annotiert, und `class Concrete implements Marker {}`. Was gibt `Concrete.class.getAnnotation(Tagged.class)` zurück?
Sie deklarieren `@Tagged` mit `@Inherited` und `@Target(ElementType.TYPE)`. `interface Marker {}` ist mit `@Tagged` annotiert, und `class Concrete implements Marker {}`. Was gibt `Concrete.class.getAnnotation(Tagged.class)` zurück?
Was this page helpful?