W3docs

Meta-Annotation in Java

Annotation che annotano altre annotation in Java — @Retention, @Target, @Documented, @Inherited, @Repeatable.

Una meta-annotation è un'annotation che si applica a una dichiarazione di annotation. Configura il comportamento di quell'annotation: quanto a lungo sopravvive, dove può comparire, se le sottoclassi la ereditano, se può essere applicata più di una volta. Ne esistono cinque in java.lang.annotation, e ogni tipo di annotation che scriverai ne utilizzerà almeno le prime due.

Le cinque:

  • @Retention — controlla se l'annotation viene mantenuta nei file .class e a runtime.
  • @Target — limita i tipi di elementi del programma che l'annotation può decorare.
  • @Documented — fa apparire l'annotation nel Javadoc generato.
  • @Inherited — fa sì che le sottoclassi ereditino le annotation a livello di classe dalla loro superclasse.
  • @Repeatable — consente di applicare la stessa annotation più di una volta allo stesso elemento.

@Retention

@Retention sceglie uno dei tre valori di RetentionPolicy:

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 { }

La scelta giusta dipende dal consumatore:

  • Il compilatore è il consumatore (controlli di override, soppressione degli avvisi, lint) → SOURCE.
  • Uno strumento di elaborazione del bytecode, un ottimizzatore JIT o un analizzatore post-compilazione è il consumatore → CLASS.
  • Un framework legge l'annotation a runtime tramite reflection (DI, ORM, binding JSON) → RUNTIME.

RUNTIME è il più permissivo — è anche il più costoso, perché ogni annotation che sopravvive aggiunge byte al file class e un piccolo overhead di reflection al momento della ricerca.

@Target

@Target limita dove l'annotation può essere posizionata. Il suo valore è un array di costanti ElementType:

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

I valori di ElementType che incontrerai:

  • TYPE — classe, interfaccia, enum, annotation.
  • METHOD, CONSTRUCTOR, FIELD, PARAMETER, LOCAL_VARIABLE.
  • ANNOTATION_TYPE — per le meta-annotation come quelle in questo capitolo.
  • PACKAGE — in package-info.java.
  • TYPE_USE (Java 8+) — qualsiasi uso di un tipo, inclusi i cast ((@NonNull String) o), le clausole extends, gli argomenti generici. Utilizzato dai controllori di nullability come Checker Framework.
  • TYPE_PARAMETER (Java 8+) — solo nelle dichiarazioni <T extends ...>.
  • MODULE (Java 9+) — in module-info.java.
  • RECORD_COMPONENT (Java 16+) — sui parametri di un'intestazione di record.

Se ometti completamente @Target, l'annotation può andare quasi ovunque — utile per marker molto generali, restrittivo per tutto il resto. Imposta quasi sempre un @Target esplicito.

@Documented

Per impostazione predefinita, le annotation non vengono mostrate nel Javadoc degli elementi che decorano. @Documented fa sì che un'annotation venga inclusa:

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

Se un metodo porta @ApiNote("rate-limited to 5 rps"), Javadoc mostrerà quell'annotation nella documentazione generata. Senza @Documented, l'annotation esiste a runtime ma è invisibile nella documentazione generata. Aggiungi @Documented a tutto ciò che ti aspetti che gli utenti della tua libreria vedano.

@Inherited

@Inherited si applica solo alle annotation che hanno come target TYPE (classi). Significa: se una classe è annotata, le sue sottoclassi vengono trattate come annotate a loro volta. Il metodo getAnnotation(...) di reflection risale la catena della superclasse e la trova.

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

@Auditable
class Account { }

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

Avvertenze:

  • Risale solo la catena delle superclassi — le interfacce non propagano le annotation anche con @Inherited. class Foo implements Auditable { } non fa sì che Foo porti un @Auditable dall'interfaccia.
  • Influisce solo su come la reflection riporta le annotation sulle classi. Metodi, campi e parametri non ereditano mai le annotation dai membri sovrascritti.

La maggior parte dei framework preferisce ora annotation esplicite e ripetute rispetto all'ereditarietà perché le regole sono più semplici. Usa @Inherited solo quando "tutto ciò che estende una classe marker è anch'esso marcato" è genuinamente ciò che vuoi.

@Repeatable

Prima di Java 8, non era possibile applicare la stessa annotation due volte allo stesso elemento. @Repeatable rimuove questa restrizione, ma la meccanica richiede attenzione. Dichiari un'annotation contenitore che contiene un array dei valori ripetuti e punti @Repeatable al contenitore:

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() { /* ... */ }
}

Regole:

  • L'elemento value del contenitore deve essere un array del tipo di annotation ripetuta.
  • Il contenitore e l'annotation ripetuta dovrebbero avere la stessa retention e almeno gli stessi target.
  • Se l'annotation ripetuta è @Documented, il contenitore deve essere @Documented anch'esso — e lo stesso vale per @Inherited. Il compilatore rifiuta una mancata corrispondenza con containing annotation interface ... is not @Documented. Mantieni le loro meta-annotation sincronizzate.
  • A runtime, il compilatore raggruppa gli usi ripetuti in una singola annotation contenitore. La reflection dispone sia di getAnnotation(Schedule.class) (restituisce il singolo elemento del contenitore quando ce ne sono due) sia di getAnnotationsByType(Schedule.class) (restituisce direttamente l'array). Usa il secondo sulle annotation @Repeatable.

Un esempio pratico: applicare tutte e cinque le meta-annotation a una vera annotation

L'esempio costruisce un piccolo sistema di annotation dall'inizio alla fine: un @Schedule che è RUNTIME, solo sui metodi, documentato e ripetibile; un marker @Module che viene ereditato dalle sottoclassi. Il metodo main le legge poi tramite reflection.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • @Module era dichiarato sulla superclasse ReportingBase, ma la reflection lo ha trovato su WeeklyReports perché l'annotation porta @Inherited. La ricerca risale la gerarchia delle classi finché non trova l'annotation o esaurisce le superclassi.
  • Il caso dell'interfaccia ha mostrato il limite dell'ereditarietà: WithInterface implementa AnnotatedInterface, che ha @Module, ma getAnnotation(Module.class) ha restituito null. @Inherited risale solo extends, mai implements. Questo mette in difficoltà molti principianti; se hai bisogno di visibilità delle annotation tra interfacce devi percorrere tu stesso l'albero dei tipi.
  • runWeekly portava due annotation @Schedule. getAnnotationsByType(Schedule.class) ha restituito un array di lunghezza 2 — il modo corretto per leggere le annotation ripetute. Il contenitore @Schedules è invisibile al codice utente se si usa getAnnotationsByType.
  • Il caso con @Schedule singolo su runDaily era simmetrico: getAnnotation(Schedule.class) ha funzionato perché non c'era contenitore, e getAnnotationsByType ha restituito un array di lunghezza 1. Entrambe le forme vanno bene quando si conosce la molteplicità.
  • Le righe del "caso ripetuto tramite getAnnotation" hanno esposto la trappola. Su runWeekly, getAnnotation(Schedule.class) ha restituito null — l'annotation effettiva nel file class è un contenitore @Schedules sintetizzato, non un Schedule. Raggiungere il contenitore tramite getAnnotation(Schedules.class) funziona. La regola: per qualsiasi annotation @Repeatable, usa sempre getAnnotationsByType in modo che i due casi (una occorrenza contro molte) appaiano identici.

Scegliere il proprio set

Quando scrivi una nuova annotation, decidi tutte e cinque in una volta sola:

  1. Chi deve leggerla? → @Retention.
  2. Dove può comparire? → @Target.
  3. Gli utenti devono vederla nel Javadoc? → @Documented oppure no.
  4. Le sottoclassi devono ereditarla? → @Inherited per marker a livello di classe come @Auditable. Ometti per le annotation a livello di metodo.
  5. Deve essere applicata più di una volta? → @Repeatable se e solo se hai genuinamente bisogno della molteplicità.

Lo scheletro predefinito per un'annotation di metodo visibile a runtime:

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

Il capitolo successivo — Annotation Personalizzate — usa esattamente questo schema per costruirne una da zero e consumarla con reflection. Per le annotation incluse nel JDK, vedi Annotation Integrate; per l'API reflection usata sopra, vedi Lettura delle Annotation con Reflection.

Pratica

Pratica
Dichiari `@Tagged` con `@Inherited` e `@Target(ElementType.TYPE)`. `interface Marker {}` è annotata con `@Tagged`, e `class Concrete implements Marker {}`. Cosa restituisce `Concrete.class.getAnnotation(Tagged.class)`?
Dichiari `@Tagged` con `@Inherited` e `@Target(ElementType.TYPE)`. `interface Marker {}` è annotata con `@Tagged`, e `class Concrete implements Marker {}`. Cosa restituisce `Concrete.class.getAnnotation(Tagged.class)`?
Was this page helpful?