Java Annotation: cosa sono, come utilizzarle, come crearle

Vediamo cosa sono le Java Annotations e come queste possono cambiare il nostro modo di programmare

Introduzione

Introdotte nella release 5 di Java (Tiger), le Java Annotation hanno piano piano preso piede e adesso è possibile trovarle in molti framework importanti. Se non avete idea di cosa stiamo parlando, le Annotation sono quelle parole precedute da un @ che sicuramente vi è capitato di vedere in qualche sorgente. Essenzialmente le Annotations sono degli strumenti che servono per descrivere le nostre classi, interfacce con informazioni aggiuntive che possono essere utili per definire valori, comportamenti, meta-data che precedentemente potevano essere esternalizzati in file di property.

Essendo uno strumento molto flessibile, non si può dire “le Java Annotation servono a …”. I modi in cui vengono utilizzate sono molteplici, quindi per il momento vediamo le Annotation già definite all’interno di Java 5, per poi vedere esempi di Annotation definite in diversi framework. Infine vedremo come realizzare Annotation definite da noi.

 

Annotation predefinite in Java

La prima Annotation che vediamo è @Override, che serve per specificare che con un determinato metodo vogliamo fare l’override del metodo presente nella classe padre.

package com.javastaff.testannotation;

public class TestOverride {
	@Override
	public String toString() {
		return "toString - TestOverride";
	}

	public static void main(String a[]) {
		TestOverride to = new TestOverride();
		System.out.println(to.toString());
	}
}

Questa Annotation è utile nel caso in cui il metodo di cui vogliamo fare l’override sia sbagliato. Praticamente se al posto del precedente metodo toString() avessimo scritto un metodo che prende come input dei parametri, toString(int i), nel momento della compilazione avremmo avuto un errore perchè non esiste un metodo toString(int i) di cui possiamo fare l’override. Vediamo ora @Deprecated, che serve a specificare che alcuni metodi sono deprecati e non dovrebbero essere utilizzati

package com.javastaff.testannotation;

public class TestDeprecated {
	@Deprecated
	public void metodoA() {
		System.out.println("Metodo DEPRECATO, usa metodoB().");
	}

	public void metodoB() {
		System.out.println("Metodo ok.");
	}

	public static void main(String a[]) {
		TestDeprecated td = new TestDeprecated();
		td.metodoA();
	}
}

In questo modo noi comunichiamo al compilatore che metodoA() è deprecato. Questa informazione viene letta anche dagli IDE, infatti di seguito potete vedere come viene evidenziato il metodo deprecato all’interno di Netbeans

Test Deprecated

 

L’ultima Annotation predefinita che vediamo è @SuppressWarnings.

package com.javastaff.testannotation;

import java.util.HashMap;
import java.util.Map;

public class TestSuppressWarning {
	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		Map map = new HashMap();
		for (String s : args) {
			map.put(s, s);
		}
	}
}

L’operazione che vediamo nel metodo main provocherebbe il seguente warning in fase di compilazione

javac com/javastaff/testannotation/TestSuppressWarning.java 

Note: com/javastaff/testannotation/TestSuppressWarning.java uses unchecked or unsafe operations.Note: Recompile with -Xlint:unchecked for details.

In alcuni casi possiamo avere la necessità di scrivere del codice “unsafe” e questa Annotation serve proprio per evitare di avere questi warning in fase di compilazione.

 

Annotation in JPA

All’interno della specifica JPA (Java Persistence API) sono presenti molte Annotation che ci permettono di evitare lunghi file di configurazione. Ad esempio nella seguente classe utilizziamo alcune Annotation definite in JPA per gestire il mapping del nostro POJO

package com.javastaff.testannotation;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@Table(name = "PHONE_REGISTRY")
public class Phone implements java.io.Serializable {
	private int id;
	private String number;
	private byte type;

	@Id
	@GeneratedValue
	@Column(name = "PHONE_ID")
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Column(name = "PHONE_NUMBER")
	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

	@Column(name = "PHONE_TYPE")
	public byte getType() {
		return type;
	}

	public void setType(byte type) {
		this.type = type;
	}
}

In questo esempio abbiamo utilizzato solo alcune delle innumerevoli Annotations che JPA definisce. Qui di seguito potete vedere un riepilogo delle altre Annotations che possiamo utilizzare

Categoria Descrizione Annotations
Entity Questa Annotation viene utilizzata
per definire un POJO come una classe
Entity e quindi come qualcosa che ha una rappresentazione sul database
@Entity
Database Schema Attributes Queste Annotation servono per meglio definire larelazione tra la nostra base dati
e la classe con cui vogliamo rappresentare
una determinata informazione
@Table

@SecondaryTable

@SecondaryTables

@Column

@JoinColumn

@JoinColumns

@PrimaryKeyJoinColumn

@PrimaryKeyJoinColumns

@JoinTable

@UniqueConstraint

Identity Attraverso l’uso di queste Annotation possiamo definire qual’è l’id della nostra classe,
gestire casi in cui abbiamo una classe come id,
identificare una sequenza che genera le informazioni per il nostro id e il modo in cui le genera
@Id

@IdClass

@EmbeddedId

@GeneratedValue

@SequenceGenerator

@TableGenerator

Direct Mappings Utilizzando queste Annotation possiamo definire ilmapping che deve essere fatto (o non essere fatto nel caso di@Transient) per alcune tipologie di campi @Basic

@Enumerated

@Temporal

@Lob

@Transient

Relationship Mappings In questo modo gestiamo le relazioni a livello diclassi e specifichiamo su quali campi fare join e quant’altro @OneToOne

@ManyToOne

@OneToMany

@ManyToMany

@MapKey

@OrderBy

Composition Queste Annotation servono per definire oggetti embeddedall’interno di Entity o per definire degli override nel mapping @Embeddable

@Embedded

@AttributeOverride

@AttributeOverrides

@AssociationOverride

@AssociationOverrides

Inheritance Annotation utili nelle classi figlie di Entity, perspecificare comportamenti differenti dalla classe padre @Inheritance

@DiscriminatorColumn

@DiscriminatorValue

@MappedSuperclass

@AttributeOverride

@AttributeOverrides

@AssociationOverride

@AssociationOverrides

Lifecycle Callback Events Annotation che gestiscono gli eventi tipici nel ciclodi vita di un Entity @PrePersist

@PostPersist

@PreRemove

@PostRemove

@PreUpdate

@PostUpdate

@PostLoad

@EntityListeners

@ExcludeDefaultListeners

@ExcludeSuperclassListeners

Entity Manager Annotation che permettono di gestire PersistenceUnit ePersistenceContext @PersistenceUnit

@PersistenceUnits

@PersistenceContext

@PersistenceContexts

@PersistenceProperty

Queries Queste Annotation servono per definire e gestire query @NamedQuery

@NamedQueries

@NamedNativeQuery

@NamedNativeQueries

@QueryHint

@ColumnResult

@EntityResult

@FieldResult

@SqlResultSetMapping

@SqlResultSetMappings

 

Come creare una propria Annotation

Per definire una nostra Annotation, dobbiamo semplicemente creare un file dove la definiamo, utilizzando la keywork @interface (invece di interface utilizzata per definire le interfacce in Java)

package com.javastaff.testannotation;

public @interface SimpleAnnotation {
}

SimpleAnnotation potrebbe essere utilizzata come semplice “Marker”, ovvero utile per etichettare determinate classi che poi dovranno essere utilizzate per fare qualcosa. Possiamo definire al loro interno anche degli attributi, con dei valori di default. Un esempio di Annotation con dei valori è ad esempio presente nella classe Phone che abbiamo visto precedentemente. L’Annotation @Table ha un attributo “name” che utilizziamo per indicare la tabella relativa al mapping. Qui di seguito possiamo vedere un Annotation con alcuni attributi.

package com.javastaff.testannotation;

public @interface AnnotationWithAttributes {
	String attributo1() default "ciao";

	String attributo2() default "mondo";
}

Per specificare il comportamento delle Annotation possiamo utilizzare altre Annotation, che giustamente vengono definite Meta-Annotation. Una di queste è @Target, che ci permette di definire a quale parte del codice può essere collegata la nostra annotation. Di seguito una tabella con tutte le possibilità, che vengono gestite tramite i valori dell’enumeration java.lang.annotation.ElementType

ElementType Descrizione
ElementType.PACKAGE Si applica alla definizione del package
ElementType.TYPE Si applica alla definizione di classi,
interfacce ed enumeration
ElementType.FIELD Si applica agli attributi
ElementType.METHOD Si applica ai metodi
ElementType.PARAMETER Si applica ai parametri dei metodi
ElementType.CONSTRUCTOR Si applica al costruttore
ElementType.LOCAL_VARIABLE Si applica ad una variabile locale

Quindi se volessimo definire un Annotation da utilizzare solo in riferimento ad un metodo, dovremmo scrivere una cosa come segue

package com.javastaff.testannotation;

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

@Target(ElementType.METHOD)
public @interface OnlyMethodAnnotation {
}

Passiamo ora a @Retention, che è la Meta-Annotation tramite la quale viene specificato come le informazioni collegate all’Annotation che stiamo definendo saranno visibili. Praticamente è possibile definire Annotation con diversi livelli di visibilità. Se vogliamo che il nostro codice esegua qualcosa in funzione dell’Annotation che un certo oggetto ha, allora dovremo utilizzare lo scope di runtime (RetentionPolicy.RUNTIME). Qui di seguito le 3 possibili opzioni che possiamo avere per questa Meta-Annotation (valori dell’enumeration java.lang.annotation.RetentionPolicy)

RetentionType Descrizione
RetentionPolicy.SOURCE Annotation presente solo a livello
di sorgente
RetentionPolicy.CLASS Annotation presente anche nel .class
ma non visibile tramite le reflection
RetentionPolicy.RUNTIME Annotation presente anche nel .class
e visibile tramite reflection

Le altre due Meta-Annotation presenti in Java sono @Documented e @Inherited. La prima serve per includere l’Annotation nel javadoc, visto che per default sono escluse. La seconda serve per far si che una classe che utilizzerà la nostra Annotation, la farà ereditare anche alle classi figlie. Eccoci quindi giunti alla creazione della nostra Annotation. Diciamo che vogliamo etichettare tutti i nostri sorgenti con un autore e una versione (non lo dobbiamo fare con le Annotation, è solo un esempio 😛 ). Scriviamo quindi un Annotation che contiene queste due informazioni

package com.javastaff.testannotation;

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

@Target(value = { ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Source {
	String autore() default "PincoPallino";

	double versione() default 0;
}

Ora la possiamo utilizzare in una nostra classe di test

package com.javastaff.testannotation;

@Source(autore = "Federico Paparoni", versione = 1.1)
public class TestClass {
	private int a;

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}
}

A questo punto potrebbe sorgere la domanda “Si ok ma ora che ci faccio con questa Annotation?”. Sicuramente con questa Annotation in particolare niente di significativo, ma dobbiamo vedere come poter recuperare le informazioni che abbiamo inserito. Questo è possibile tramite le Reflection API, come illustrato nella seguente classe

package com.javastaff.testannotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

public class AnnotationReader {
	public static void main(String args[]) throws Exception {
		readAnnotation(TestClass.class);
	}

	static void readAnnotation(AnnotatedElement element) {
		try {
			Annotation[] classAnnotations = element.getAnnotations();
			for (Annotation annotation : classAnnotations) {
				if (annotation instanceof Source) {
					Source source = (Source) annotation;
					System.out.println("Autore:" + source.autore());
					System.out.println("Versione:" + source.versione());
				}
			}
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}
}

In questo caso andiamo ad analizzare la classe per stampare semplicemente le informazioni che abbiamo memorizzato tramite Annotation. In altri casi le informazioni che vengono gestite tramite Annotation possono definire, come nel caso di JPA, una gestione che si deve avere di una particolare classe in relazione ad un mapping sul database.

 

Conclusioni

Le Annotation sono molto potenti e permettono di gestire molte situazioni in maniera elegante e pulita. Da quando Java 5 ha introdotto questa feature abbiamo infatti visto molti frameowrk, API farne uso (JPA, JAX-WS, Spring, EJB3).

 

Riferimenti

Java Annotations
Writing Performant EJB Beans in the Java EE 5 Platform (EJB 3.0) Using Annotations
Web Service tramite Java Annotations
Toplink JPA Annotations
JPA Javadoc

Federico Paparoni

Looking for a right "about me"...

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.