W3docs

Introduzione alle Annotazioni Java

Cosa sono le annotazioni Java, come aggiungono metadati al codice e dove vengono elaborate.

Un'annotazione è un marcatore che si applica a un elemento del sorgente Java — una classe, un metodo, un campo, un parametro, un uso di tipo — per aggiungere metadati. L'annotazione in sé non fa nulla. È un'etichetta che qualche altro pezzo di codice (il compilatore, un framework, un IDE, uno strumento di build) legge successivamente e risponde. @Override su un metodo dice al compilatore "sto sovrascrivendo un metodo della superclasse; avvertimi se non è così." @Test su un metodo dice a JUnit "questo è un test." @Entity su una classe dice a JPA "mappa questa su una tabella del database." In ogni caso l'annotazione porta solo informazioni; il comportamento vive nel processore che la legge.

Hai già visto le annotazioni in tutto questo libro — @Override sui metodi sovrascritti, @FunctionalInterface sulle interfacce con un solo metodo, @SuppressWarnings per silenziare il compilatore. Questa parte del libro riguarda il sistema sottostante: cosa è un'annotazione, quando è disponibile (solo sorgente, file di classe o runtime), come scriverne una propria e come i processori la consumano.

La forma di un'annotazione

Sintatticamente, un'annotazione è il simbolo @ seguito dal nome dell'annotazione, applicata immediatamente prima dell'elemento che annota:

@Override
public String toString() { ... }

@Deprecated
public void oldApi() { ... }

@SuppressWarnings("unchecked")
List<String> list = (List<String>) raw;

Alcune annotazioni accettano elementi (la loro versione dei campi). I valori degli elementi vanno tra parentesi:

@SuppressWarnings("unchecked")                       // one element, value "unchecked"
@SuppressWarnings({"unchecked", "rawtypes"})         // array of strings

@RequestMapping(path = "/users", method = GET)       // two named elements

Se un'annotazione dichiara un singolo elemento chiamato value, puoi omettere il nome — @SuppressWarnings("unchecked") è un'abbreviazione di @SuppressWarnings(value = "unchecked").

Cosa le annotazioni non sono

Tre negazioni che evitano i malintesi più comuni:

  • Le annotazioni non eseguono codice. Scrivere @Cached vicino a un metodo non mette nulla in cache. Qualcos'altro deve cercare @Cached e aggiungere il comportamento di caching. L'annotazione è un flag, non una funzione.
  • Le annotazioni non sono commenti. I commenti scompaiono al momento della compilazione; le annotazioni sono costrutti del linguaggio di prima classe. Partecipano al sistema dei tipi, possono essere richieste per vivere nel file di classe e possono essere lette a runtime via reflection.
  • Le annotazioni non sono un sostituto per un codice chiaro. Una lunga serie di annotazioni sopra una classe è densità di informazioni, non sempre chiarezza. I framework che si affidano pesantemente alle annotazioni (Spring, JPA, JAX-RS) pagano per la comodità con una curva di apprendimento e un costo a runtime.

Quando vive l'annotazione

Ogni annotazione ha una politica di retention che decide quanto a lungo i metadati sopravvivono:

  • SOURCE — il compilatore la legge e poi la scarta. @Override e @SuppressWarnings funzionano così; il bytecode non contiene alcuna traccia di loro.
  • CLASS — l'annotazione viene scritta nel file .class ma non caricata dalla JVM a runtime. Questo è il valore predefinito. Gli strumenti che ispezionano il bytecode (analizzatori statici, post-processori) possono leggerla.
  • RUNTIME — l'annotazione viene mantenuta fino alla fine; la reflection può chiedere a qualsiasi classe, metodo o campo le sue annotazioni a runtime. Framework come Spring e Jackson si affidano a questo.

Vedrai la meta-annotazione @Retention(...) che imposta questa politica nel capitolo sulle Meta-annotazioni. In breve: scegli la retention in base a chi ha bisogno di leggere l'annotazione — il compilatore, uno strumento a tempo di build o il codice a runtime.

Dove può andare l'annotazione

Ogni annotazione ha anche un target — l'insieme degli elementi del programma che può annotare legalmente. I target comuni:

  • TYPE — classi, interfacce, enum.
  • METHOD — metodi.
  • FIELD — campi.
  • PARAMETER — parametri di metodo.
  • CONSTRUCTOR — costruttori.
  • LOCAL_VARIABLE — dichiarazioni di variabili locali.
  • ANNOTATION_TYPE — altre dichiarazioni di annotazioni (meta-annotazioni).
  • PACKAGE — package, tramite package-info.java.
  • TYPE_USEqualsiasi uso di un tipo, inclusi i parametri generici e i cast (Java 8+).

Se inserisci un'annotazione dove il suo @Target non lo consente, il compilatore rifiuta. Provare @Override su una dichiarazione di classe è un errore di compilazione perché @Override ha come target METHOD.

Chi legge le annotazioni

Tre posti consumano i dati delle annotazioni:

  1. Il compilatore. Le annotazioni integrate come @Override, @SafeVarargs e @FunctionalInterface vengono controllate da javac stesso.
  2. I processori di annotazioni. Strumenti collegabili a tempo di compilazione che vengono eseguiti durante javac. Possono leggere le annotazioni sui sorgenti in compilazione e generare nuovi file sorgente in risposta. Lombok, Dagger, il metamodello statico di Hibernate e il framework Micronaut funzionano tutti in questo modo.
  3. La reflection a runtime. Method.getAnnotations(), Class.getAnnotation(...), ecc. restituiscono le istanze di annotazione per qualsiasi elemento con retention RUNTIME. È così che Spring decide cosa iniettare, come JUnit trova i tuoi test e come Jackson mappa il JSON.

I primi due non richiedono supporto della macchina virtuale oltre a quello fornito da javac. Il terzo richiede che l'annotazione sia scritta nel file di classe e caricata dal runtime.

Un esempio pratico: ispezionare le annotazioni sulla propria classe

Lo scopo di questo esempio è mostrare che @Override, @Deprecated e @SuppressWarnings sembrano identici nel sorgente ma si comportano diversamente una volta compilata la classe. Il programma dichiara una classe con diverse annotazioni, poi chiede alla reflection cosa riesce effettivamente a vedere.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il ciclo a livello di classe ha visto @Deprecated su Greeter ma nient'altro — @Deprecated ha retention RUNTIME. @Override e @SuppressWarnings sono SOURCE, quindi il compilatore li ha eliminati prima che il file di classe venisse scritto e la reflection non può recuperarli.
  • Il ciclo per metodo ha stampato @Deprecated solo su oldHello. Anche se toString era dichiarato @Override e cast era dichiarato @SuppressWarnings("unchecked"), nessuna delle due annotazioni è arrivata al file di classe. L'informazione esisteva nel momento in cui javac girava — è così che è avvenuto il controllo dell'override — e poi è stata scartata.
  • Il controllo della retention lo ha chiarito: @Override e @SuppressWarnings portano @Retention(SOURCE) nelle proprie dichiarazioni, mentre @Deprecated porta @Retention(RUNTIME). La retention è una proprietà del tipo di annotazione, non di come viene usata.
  • Leggere @Deprecated su Greeter tramite cls.getAnnotation(Deprecated.class) ha restituito un proxy i cui metodi elemento (since(), forRemoval()) hanno restituito i valori scritti nel sorgente. Questa è l'interfaccia runtime per i metadati delle annotazioni: un'istanza i cui elementi sono metodi accessor.
  • La conclusione per scegliere la retention: se l'unico consumatore è javac (controlli di override, soppressione degli avvisi), usa SOURCE. Se un framework ha bisogno di leggere l'annotazione mentre il programma è in esecuzione (DI, ORM, binding JSON), usa RUNTIME. Il capitolo sulle meta-annotazioni spiega come dichiarare questo per i tuoi tipi di annotazione personalizzati.

Cosa verrà in questa parte

I capitoli rimanenti in questa parte ti guideranno attraverso:

  • Le annotazioni integrate più comuni in java.lang@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.
  • Le meta-annotazioni che configurano le tue — @Retention, @Target, @Documented, @Inherited, @Repeatable.
  • Scrivere tipi di annotazione personalizzati e leggerli via reflection.
  • L'API di elaborazione delle annotazioni a tempo di compilazione che i framework usano per generare codice.

Il percorso va da "quali annotazioni il linguaggio ti fornisce" a "quali annotazioni puoi costruire su di esse."

Pratica

Pratica
Un collega scrive `@Cached` accanto a un metodo e si aspetta che la seconda chiamata restituisca un risultato dalla cache. Cosa ha sbagliato riguardo alle annotazioni?
Un collega scrive `@Cached` accanto a un metodo e si aspetta che la seconda chiamata restituisca un risultato dalla cache. Cosa ha sbagliato riguardo alle annotazioni?
Was this page helpful?