Semplificare lo sviluppo delle applicazioni è stato da sempre un importante obiettivo dei linguaggi di programmazione e di chi li crea.
Java, da questo punto di vista, si è sempre prestato a modifiche più o meno sostanziali, cercando di aderire a quelle che sono le esigenze più sentite dalla sua enorme comunità di sviluppatori.
Il passaggio dalla versione J2SE 1.4 alla versione 1.5, nota anche come JSE 5, ha segnato un passaggio netto verso un nuovo modello di produzione del codice con il supporto di nuove caratteristiche. Discuteremo in questo articolo di quella che probabilmente è la più importante di esse: l'introduzione delle Annotations (o, in italiano, Annotazioni).
Possiamo definire un'annotation come un appunto che mettiamo per specificare qualcosa relativo al codice che stiamo scrivendo, un attributo particolare, un metodo o una classe che hanno delle peculiarità. Attraverso questo meccanismo siamo capaci di dare espressività al codice, di renderlo più leggibile agli occhi di altri sviluppatori ma soprattutto agli occhi del compilatore.
Le annotations, infatti, sono delle annotazioni per il compilatore (o per chi si occupa del deploy dell'applicazione) che, attraverso di esse avrà la possibilità di effettuare determinate operazioni.
Questo paradigma, oltre a rendere più espressivo il codice sorgente, permette una migliore manutenzione dello stesso. Utilizzando le opportune annotations, infatti, riusciremo ad evitare possibili errori di compilazione, oppure, meglio ancora, potremo delegare a strumenti esterni la configurazione dell'applicazione che stiamo scrivendo (nell'ultima parte dell'articolo vedremo le annotations in ambiente enterprise).
La stessa Sun definisce gli usi delle annotazioni come di seguito:
- Per informare il compilatore
- Per processare a tempo di compilazione (o di deploy)
- Per processare a run time
Le ultime due caratteristiche sono tipiche degli ambienti enterprise, che le possono utilizzare mediante reflection.
Annotazioni previste da JDK 1.5
Un'annotazione si presenta nella seguente forma:
Listato 1. Forma di un'annotazione
@Autore(
name = "Pasquale Congiustì",
company = "HTML.it"
)
class ClasseAnnotata() {
...
}
Ogni annotazione si presenta con il simbolo @ seguito dal nome dell'annotazione. Eventualmente può essere valorizzata con dei valori, tra parentesi tonde come coppia nome-valore. Essa precede la classe, il metodo o l'attributo che vogliamo annotare.
In questo esempio abbiamo annotato la classe con l'annotation Autore ed i due attributi name e company.
Prima di mostrare come creare nuove annotazioni, vediamo quelle di default fornite a partire dalla distribuzione J2SE 1.5. Si tratta di tre semplici annotazioni utili al compilatore.
Attenzione: utilizzare le annotazioni significherà che il codice non può essere compilato (e spesso anche eseguito) con versioni Java precedenti.
@Deprecated
L'annotazione @Deprecated viene utilizzata per specificare che l'elemento indicato è un elemento deprecato, cioè, attivo (per mantenere retrocompatibilità) ma non consigliato perché rimpiazzato da uno nuovo e supportato.
Listato 2. Esempio con un annotazione @Deprecated
public class TestDeprecated {
@Deprecated
public void metodoA() {
System.out.println("Questo metodo è DEPRECATO, usa metodoB().");
}
public void metodoB() {
System.out.println("Questo metodo è SUPPORTATO.");
}
}
La compilazione di questa classe non darà alcun segnale, procederà tutto normalmente. Sarà la compilazione della classe che userà TestDeprecated a ricevere segnalazioni di warning dal compilatore quando viene utilizzato il metodo metodoA()
.
...
TestDeprecated td=new TestDeprecated();
...
Td.metodoA();
...
@Override
L'annotation @Override è probabilmente la più utile in quanto consente di evitare degli errori, che in fase di codifica spesso accadono. L'annotazione dice che l'elemento indicato è un elemento che fa l'override (sovrascrive) del relativo elemento, del genitore da cui eredita.
L'esempio ci permetterà di capire meglio.
Listato 3. Esempio di Override
class A{
void metodo1(){
System.out.println("Metodo 1");
}
}
class B extends A{
@Override
void metodoo1(){
System.out.println("Override A.metodo1()");
}
}
Abbiamo la classe genitore A, che presenta un metodo, metodo1()
. Creiamo una classe B, erede di A. Vogliamo fare l'override di metodo1()
, quindi annotiamo il metodo presente nella classe B con l'annotazione @Override, indicando che il metodo annotato è un metodo che sovrascrive un metodo del genitore.
Se provate a compilare il codice, il compilatore vi restituirà un errore. Se notate, infatti, ho inserito un errore di battitura nel nome del metodo. Senza l'annotazione @Override la compilazione sarebbe andata a buon fine e non ci saremmo accorti dell'errore.
@SuppressWarning
L'annotazione @SuppressWarning è utile quando vogliamo sopprimere le indicazioni di warning da parte del compilatore, ad esempio, perché stiamo usando dei metodi deprecati.
Listato 4. Esempio di SuppressWarning
...
@SuppressWarnings({"deprecation"})
public void usaMetodoDeprecato() {
TestDeprecated t = new TestDeprecated();
t.metodoA();
}
...
Pur usando dei metodi deprecated, al compilatore abbiamo segnalato di sopprimere i warning.
Creare nuove annotazioni
Le annotazioni di default sono di sicuro interesse in quanto permettono di migliorare alcuni aspetti nelle fasi di compilazione, facendo si che lo sviluppatore possa fare a meno di preoccuparsi di alcuni potenziali errori di codifica.
Attraverso il meccanismo, che andremo a spiegare con un esempio, JDK 5 permette allo sviluppatore di definire delle proprie annotazioni totalmente customizzabili. Si tratta semplicemente di definire il nome dell'annotazione e di alcune proprietà che la contraddistinguono.
La cosa che rende le annotations uno strumento davvero importante tanto da alterare il tipico paradigma di programmazione, è la possibilità di effettuare introspezione del codice. Con la reflection è possibile valutare a runtime quali annotations sono presenti (e quali valori hanno in esse) e quindi effettuare determinate operazioni. Possiamo pensare ad un framework che gestica la persistenza con un database, dove all'interno del codice sono presenti delle annotations che indicano come mappare attributi di classe su colonne di database. Oppure utilizzare uno strumento personalizzato per team di sviluppo per commentare opportunamente il codice, chi ne modifica i metodi, chi li crea e quando e così via, per mantenere traccia del lavoro svolto.
Vediamo ora, con un esempio concreto, come creare un'annotazione personalizzata. Creeremo una semplice annotazione con alcuni campi e vedremo anche come leggerla a runtime.
Listato 5. Crea e legge un'annotazione
package it.html.annotations;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Una semplice annotation, usata per segnare gli elementi
* (classe, attributo, metodo), l'autore di quell'elemento e
* l'azienda.
*/
@Retention(RUNTIME)
public @interface MyAnnotation {
public String value();
public String author();
public String company() default "HTML.it";
}
L'annotazione viene creata con il comando @interface, seguito dal nome dell'annotazione stessa. Badate che si tratta di classi java, pertanto esse seguono le stesse regole relative a package e import.
L'annotazione è (nel nostro caso) preceduta da un'altra annotazione, @Retention, che specifica il livello di introspezione desiderato. Ne esistono tre: RUNTIME, CLASS e SOURCE.
Usate RUNTIME se volete che l'annotazione sia utilizzabile dalla jvm, durante l'esecuzione (a runtime, appunto). Usate CLASS se volete che l'annotazione sia visibile solo dal compilatore. Usate SOURCE se l'annotazione non deve essere visibile (quindi usata meramente come commento al codice sorgente).
Il corpo dell'annotation può contenere, o meno, delle proprietà. Nel secondo caso si parla di marker annotation, utilizzate per marcare uno specifico elemento. Il primo caso è quello che ci riguarda e, come vediamo dall'esempio, è molto simile a quanto si fa con le interfacce.
Noi abbiamo definito tre parametri, tutti e tre stringa nominati: value, author e company.
I tipi che possono essere utilizzati sono i tipi primitivi (i numeri), le stringhe, enum, class, altre annotazioni ed array dei tipi appena menzionati. Una possibilità è quella di definire dei valori di default, come abbiamo fatto nel caso del parametro company.
Ultima cosa da dire, prima di utilizzare l'annotazione appena creata, è che c'è la possibilità di creare le annotazioni per specifiche parti della classe. L'annotazione può essere creata avendo come target solo i metodi, oppure solo i costruttori. Potrebbe avere come target solo la classe o i suoi attributi. In tal caso si usa un'altra annotation, @Target, che specifica appunto il tipo per cui l'annotation è stata pensata. Se avessimo voluto utilizzarla avremmo dovuto scrivere:
@Retention(value=RUNTIME)
@Target(value=XYZ)
public @interface MyAnnotation{
...
Dove il valore XYZ doveva essere rimpiazzato con: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE. Tutto dipende dal fatto di voler forzare un'annotazione ad uno specifico tipo.
Andiamo a vedere una classe che viene "annotata" attraverso l'annotazione appena creata.
Listato 6. Una classe che usa l'annotazione
package it.html.annotations;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//Qui la usiamo a livello di classe
@MyAnnotation(value="class",author="Pippo")
public class SimpleAnnotationUse {
//qui, a livello di attributo
@MyAnnotation(value="attribute",author="Pippo")
public String saluto;
//qui, a livello di costruttore
@MyAnnotation(value="costruttore",author="Pippo")
public SimpleAnnotationUse(){
this.saluto="Hello World!";
}
//qui, a livello di metodo
@MyAnnotation(value="method",author="Zio Paperone",company="Gugol")
public String getSaluto(){
return saluto;
}
...
}
Possiamo vedere che ogni elemento della classe (e la classe stessa) vengono annotati con il valore indicante l'elemento stesso, il nome dell'autore e l'azienda (laddove l'azienda manca, verrà recuperato il valore di default).
Listato 7. Classe che fa introspezione della classe, dei metodi e degli attributi
public static void main(String args[]) throws NoSuchMethodException, NoSuchFieldException{
System.out.println("Working Annotations...");
//Accesso alla classe
Class<SimpleAnnotationUse> c = SimpleAnnotationUse.class;
//Mostra annotazioni
System.out.println("Annotazione di classe: ");
System.out.println(c.getAnnotation( MyAnnotation.class ));
//Metodo costruttore
Constructor<SimpleAnnotationUse> constructor = c.getConstructor((Class[]) null);
System.out.println("Annotazione costruttore: ");
System.out.println(constructor.getAnnotation(MyAnnotation.class));
//Metodo getSaluto
Method method = c.getMethod( "getSaluto" );
System.out.println("Annotazione metodo getSaluto(): ");
System.out.println(method.getAnnotation(MyAnnotation.class));
//Campo saluto
Field field = c.getField("saluto");
System.out.println("Annotazione attributo saluto: ");
System.out.println(field.getAnnotation(MyAnnotation.class));
}
Il main proposto fa introspezione della classe, dei metodi e degli attributi, utilizzando il metodo getAnnotation()
. In questo caso stamperemo semplicemente il valore, ma i casi d'uso che si aprono sono veramente innumerevoli.
Dove usare le annotazioni
È chiaro, a questo punto, come l'utilizzo opportuno di annotation sia capace di dare tanta espressività in più al codice prodotto. Il reale vantaggio che si ha è quando tale strumento viene affiancato da altri strumenti che fanno introspezione del codice e, in base ad esso, creano delle configurazioni per framework.
È proprio in queste situazioni, infatti, che le annotations hanno un notevole vantaggio. Prima citavamo framework per la persistenza automatizzata, è il caso di Hibernate, che permette di mappare classi e tabelle attraverso descrittori XML. Ora, attraverso l'uso di annotation, non sarà più necessario creare dei descrittori di configurazione, bensì, adottare delle annotazioni che suggeriscano l'associazione direttamente all'interno del codice.
Mantenere descrittori di configurazione opportunamente allineati può essere un problema, in quanto si tratta di file esterni (generalmente XML), difficilmente leggibili dall'occhio umano.
È il caso anche degli EJB3.0 che, dalle annotazioni, traggono un notevole vantaggio, rendendo più veloce e meno incline ad errori la produzioni di logica applicativa enterprise.