Come definire e utilizzare a runtime delle custom Annotation

DOMANDA:

Come si definiscono delle Annotation custom per utilizzarle nel mio programma?



RISPOSTA:

Le Annotation introdotte in Java 5 consentono di definire dei metadata per tre diversi livelli di scoping:
  1. SOURCE - Annotation disponibile solo nello scope del sorgente e viene ignorato dal compilatore
  2. CLASS - Annotation disponibile nello scope dal compilatore in fase di compilazione, ma viene ignorata dalla Java Virtual Machine (JVM)
  3. RUNTIME - Annotation disponibile nello scope della JVM in modo che possa essere utilizzato da l'ambiente di runtime
Le Annotation NON aggiungono logica all'applicazione e non sono uno strumento per programmare componenti. Sono invece in grado di fornire delle informazioni (metadati) per capire meglio il tipo di codice che stiamo eseguendo ed eventualmente intraprendere delle azioni o eseguire delle configurazioni).

In questo post ci concentreremo sulle Annotation di tipo RUNTIME per riuscire ad eseguire delle azioni durante l'esecuzione dell'applicazione.


Innanzitutto realizziamo un file di properties e lo chiamiamo (ad esempio) city.properties. Il contenuto sarà semplice:

name=Roma
country=Italia


Adesso realizziamo un bean City che legge delle informazioni dal file di properties appena creato:
package lancill.test.pojo;

import java.io.FileInputStream;
import java.util.Properties;
import lancill.annotation.ReadPropertiesAnnotation;

public class City {
 
 private Properties props = new Properties();
 private final String PROPERTIES_FILE_NAME = "city.properties";
 public City(){
  try {
   props.load(new FileInputStream(PROPERTIES_FILE_NAME));
  } catch (Exception e) {
   props.setProperty("name","N/A");
   props.setProperty("country","N/A");
   e.printStackTrace();
  }
 }
 
 public String getName(){
  return props.getProperty("name");
 }
 
 public String getCountry(){
  return props.getProperty("country");
 }
}


Il prossimo passo è realizzare una Annotation custom che ci consentirà di capire qual è il file di properties su cui si basa il bean City e quali sono i valori delle property che utilizzerà il bean a runtime.

Definiamo la nostra Annotation ReadPropertiesAnnotation nel seguente modo:
package lancill.annotation;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadPropertiesAnnotation {
 
 String fileName();
 String propertyName() default "N/A";

}
Vediamo quali sono le parole chiave che descrivono l'Annotation:
  • @interface consente di definire la classe ReadPropertiesAnnotation di tipo Annotation
  • @Target indica che l'Annotation che stiamo definendo può essere applicata solo ai metodi
  • @Retention definisce il livello di scope (in questo RUNTIME)
  • la @Retention deve essere obbligatoriamente RUNTIME se vogliamo che le informazioni della nostra Annotation siano  disponibili durante l'esecuzione dell'applicazione 
Applichiamo ora la nostra Annotation ai metodi del bean City:
package lancill.test.pojo;

import java.io.FileInputStream;
import java.util.Properties;
import lancill.annotation.ReadPropertiesAnnotation;

public class City {
 
 private Properties props = new Properties();
 private final String PROPERTIES_FILE_NAME = "city.properties";
 public City(){
  try {
   props.load(new FileInputStream(PROPERTIES_FILE_NAME));
  } catch (Exception e) {
   props.setProperty("name","N/A");
   props.setProperty("country","N/A");
   e.printStackTrace();
  }
 }
 
 @ReadPropertiesAnnotation (fileName=PROPERTIES_FILE_NAME, propertyName="name")
 public String getName(){
  return props.getProperty("name");
 }
 
 @ReadPropertiesAnnotation (fileName=PROPERTIES_FILE_NAME, propertyName="country")
 public String getCountry(){
  return props.getProperty("country");
 }
}


A questo punto dobbiamo definire una classe che sia in grado di recuperare le informazioni delle nostra Annotation e interpretarle correttamente.

Definiamo quindi la classe AnnotationAnalyzer che, attraverso il metodo analyze(), recupera il bean City e i relativi metodi con le annotation di tipo ReadPropertiesAnnotation.

Una volta individuati i metodi, carica l'annotation ed esegue il parsing delle informazioni:
package lancill.annotation.parser;

import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Properties;
import lancill.annotation.ReadPropertiesAnnotation;

public class AnnotationAnalyzer {
    
 public void analyze() {

  try {
   Class annotationClass = AnnotationAnalyzer.class.getClassLoader()
     .loadClass(("lancill.test.pojo.City"));
   
   for (Method method : annotationClass.getMethods()) {
    if (method
      .isAnnotationPresent(lancill.annotation.ReadPropertiesAnnotation.class)) {

     try {
      ReadPropertiesAnnotation metodoAnnotation = method
        .getAnnotation(ReadPropertiesAnnotation.class);

      if (!metodoAnnotation.fileName().isEmpty()) {
       System.out.println("Annotation @ReadPropertiesAnnotation:\n");
       System.out.println("\t- Metodo: "+ method);
       
       System.out.println("\t- Nome file di properties: "+ metodoAnnotation.fileName());
       System.out.println("\t- Property associata al metodo: "+  metodoAnnotation.propertyName());
       System.out.println("\t- Valore restituito dal metodo: "+ getPropertyValue(metodoAnnotation.fileName(),metodoAnnotation.propertyName())+"\n");
      }

     } catch (Throwable ex) {
      ex.printStackTrace();
     }
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 private String getPropertyValue(String fileName, String propertyName) {
  Properties props=new Properties();
  
  try {
   props.load(new FileInputStream(fileName));
   return props.getProperty(propertyName);
  } catch (Exception e) {
   e.printStackTrace();
  }
  
  return "Impossibile calcolare il valore";
 }
}

Possiamo ora definire una semplice applicazione che lancia il metodo analyze():
package lancill.test;

import lancill.annotation.parser.AnnotationAnalyzer;

public class TestAnnotation {

 public static void main(String[] args) {
  AnnotationAnalyzer aa = new AnnotationAnalyzer();
  aa.analyze();
 }
}

L'output sarà il seguente:
Annotation @ReadPropertiesAnnotation:
- Metodo: public java.lang.String lancill.test.pojo.City.getName()
- Nome file di properties: city.properties
- Property associata al metodo: name
- Valore restituito dal metodo: Roma
Annotation @ReadPropertiesAnnotation:
- Metodo: public java.lang.String lancill.test.pojo.City.getCountry()
- Nome file di properties: city.properties
- Property associata al metodo: country
- Valore restituito dal metodo: Italia




Riferimenti



Commenti

Post popolari in questo blog

Arrotondamento e troncamento in Java

Eclipse: Shortcuts (scorciatoie) da tastiera

Strutture dati: List, Set, Map

Creare un eseguibile Java