Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Coder un nouveau type d'annotations

Introduction à l'utilisation d'annotations Coder un mini framework de test avec les annotations



Accès rapide :
Définition d'une nouvelle annotation
Syntaxe générale
Les différents niveaux de rétention
Ajout d'une cible à une annotation
Ajout d'attributs à une annotation
Manipulation de notre annotation
Travaux pratiques
Le sujet
La correction

Définition d'une nouvelle annotation

Syntaxe générale

Il est possible de coder ses propres d'annotations. On procède à ce type de définitions quand on cherche à développer son propre framework de test, de persistance ou autre.

Une annotation se définit via la construction @interface. On doit donner un nom à l'annotation : les conventions de nommage à respecter sont les mêmes que pour les classes ou les interfaces. Voici un premier exemple de définition d'annotation : cette annotation pourrait être utilisée par un framework de test similaire à JUnit (ce que nous étudierons dans le chapitre suivant).

 1 
 2 
 3 
 4 
 5 
 6 
package fr.koor.sample;


public @interface TestMethod {
        
}
Notre première annotation

Les différents niveaux de rétention

Il existe différents niveaux de « rétention » : la rétention d'une annotation correspond, dans une certaine mesure, à sa durée de vie. On spécifie la rétention d'une annotation en lui ajoutant l'annotation @Retention (oui, une annotation peut être appliquée à une autre annotation). Voici un exemple de définition de la rétention d'une annotation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
package fr.koor.sample;

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

@Retention( value = RetentionPolicy.RUNTIME )
public @interface TestMethod {
        
}
Définition de la rétention pour notre annotation
comme vous le constatez, l'annotation @Retention ne possède qu'un seul et unique attribut. Si tel est le cas, vous pouvez omettre le nom de cet unique attribut. En conséquence, le code suivant est strictement équivalent.
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
package fr.koor.sample;

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

@Retention( RetentionPolicy.RUNTIME )
public @interface TestMethod {
        
}
Définition de la rétention pour notre annotation

Et voici la liste des différents niveaux de rétention supportés par Java.

Ajout d'une cible à une annotation

Il est possible de définir sur quel type d'éléments on peut appliquer une annotation (classes, méthodes, attributs, ...). Pour ce faire, il faut ajouter l'annotation @Target à votre annotation. L'annotation @Target n'accepte qu'un seul et unique attribut nommé value : en fait, c'est lui qui permet de spécifier la cible.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
package fr.koor.sample;

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

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface TestMethod {
        
}
Définition de la cible pour notre annotation (ici, des méthodes)
A partir de maintenant, si vous cherchez à appliquer votre annotation @TestMethod à autre chose qu'une méthode, une erreur de compilation sera produite.

Voici la liste des cibles d'annotations supportées par Java :

Il est aussi possible de définir plusieurs cibles pour une annotation. C'est par exemple le cas de l'annotation @Deprecated qui peut être apposée sur un grand nombre d'éléments.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
import static java.lang.annotation.ElementType.*;
            
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    // ...
}
Exemple de définition d'une cible multiple.

Ajout d'attributs à une annotation

Une annotation est une méta-données qu'on va appliquer à un élément de votre programme. Parfois, cette méta-donnée est complexe et il est nécessaire de spécifier plusieurs de ses caractéristiques. Pour ce faire, une annotation supporte des attributs. Par exemple, l'annotation @Target vue dans la section précédente, possède un attribut value nous permettant de lister l'ensemble des cibles auxquelles il est potentiellement possible d'appliquer l'annotation.

Dans le cas de notre annotation @TestMethod, on va partir du principe qu'il est possible de s'attendre à une éventuelle remontée d'exception. Nous allons aussi permettre la définition d'un temps d'exécution maximal de notre méthode de test. Voici comment ajouter ces deux attributs.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
package fr.koor.sample;

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

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface TestMethod {

    Class<? extends Throwable> expected();
    
    long timeout();
    
}
Définition de deux attributs sur notre annotation
la syntaxe utilisée pour définir un attribut d'une annotation est sensiblement différente de celle utilisée pour définir un attribut de classe.

Il est aussi possible de définir des valeurs par défaut sur les attributs d'une annotation. Il ne sera ainsi pas obligatoire de spécifier une valeur.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
package fr.koor.sample;

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

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface TestMethod {

    /** Permet de spécifier un éventuel type d'exception attendue */ 
    Class<? extends Throwable> expected() default NoExceptionExpected.class;
    
    /** Un temps maximal d'exécution de notre scénario de test */
    long timeout() default -1;
    
    /** Un type que nous associerons à une situation ou aucune exception n'est attendue. */
    public static class NoExceptionExpected extends Throwable {}
    
}
Définition de deux attributs, avec valeurs par défaut, sur notre annotation

Manipulation de notre annotation

Voici maintenant un exemple de code permettant de savoir si une méthode porte une annotation particulière. Cela se fait grâce au moteur de réflexion du langage. Une fois une annotation obtenue, il est possible de la manipuler comme un objet Java classique. Il faut néanmoins noter la syntaxe particulière permettant l'accès aux attributs de l'annotation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
package fr.koor.sample;

import java.lang.reflect.Method;

import fr.koor.sample.TestMethod.NoExceptionExpected;

public class Demo {

    @TestMethod
    public void firstMethod() { System.out.println( "Is a test method" ); }

    public void secondMethod() { System.out.println( "Is not a test method" ); }

    @TestMethod( timeout = 5 )
    public void thirdMethod() { System.out.println( "Is a test method" ); }

    @TestMethod( expected = ArithmeticException.class, timeout = 1 )
    public void fourthMethod() { System.out.println( "Is a test method" ); }

    
    public static void main( String [] args ) throws Exception {

        // On récupère les méta-données de la classe Demo
        Class<Demo> metadata = Demo.class;
        
        // On parcourt toutes les méthodes de la classe Demo, y compris le main.
        for ( Method method : metadata.getDeclaredMethods() ) {
            
            // On vérifie la présence de notre annotation
            TestMethod annotation = method.getAnnotation( TestMethod.class );
            if ( annotation == null ) continue;
            
            // On affiche le nom de la méthode ainsi que les attributs de l'annotation.
            System.out.printf( "%s - %s - max duration = %d\n",
                    method.getName(),
                    annotation.expected() != NoExceptionExpected.class ? 
                            annotation.expected().getName() : "no exception expected",
                    annotation.timeout()
            );
        }
        
    }

}
Exemple de code recherchant une annotation particulière.

Voici les résultats qui s'afficheront si vous exécutez ce programme.

firstMethod - no exception expected - max duration = -1
thirdMethod - no exception expected - max duration = 5
fourthMethod - java.lang.ArithmeticException - max duration = 1

Travaux pratiques

Le sujet

Veuillez créer une nouvelle annotation, appelée TestClass permettant de marquer les classes de tests. Cette annotation doit définir un seul et unique attribut appelé category (il s'agit d'une simple chaîne de caractères) : il permettra de classifier les classes de test.

Dans un second temps, reprendre le code de la classe Demo vue ci-dessus, et ajouter l'annotation sur cette classe. Ensuite, vérifier que l'instance metadata détecte bien la présence de notre annotation.

dans le chapitre suivant, nous utiliserons cette annotation, ainsi que l'annotation TestMethod pour bâtir un petit framework de tests unitaires.

A vous de jouer et essayez de ne pas regarder la correction trop vite ;-)

La correction

Voici le code de l'annotation TestClass demandée.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
package fr.koor.sample;

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

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface TestClass {

    String category() default "default";
    
}

Pour utiliser cette classe, vous pouvez compléter la classe de démonstration, vue précédemment, ainsi :

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
package fr.koor.sample;

import java.lang.reflect.Method;

import fr.koor.sample.TestMethod.NoExceptionExpected;

@TestClass( category = "demo" )
public class Demo {

    @TestMethod
    public void firstMethod() { System.out.println( "Is a test method" ); }

    public void secondMethod() { System.out.println( "Is not a test method" ); }

    @TestMethod( timeout = 5 )
    public void thirdMethod() { System.out.println( "Is a test method" ); }

    @TestMethod( expected = ArithmeticException.class, timeout = 1 )
    public void fourthMethod() { System.out.println( "Is a test method" ); }

    
    public static void main( String [] args ) throws Exception {

        // On récupère les méta-données de la classe Demo
        Class<Demo> metadata = Demo.class;
        
        // Vérification de la présence de notre annotation sur la classe.
        if ( metadata.getDeclaredAnnotation( TestClass.class ) == null ) {
            System.err.println( "@TestClass annotation is not present" );
            System.exit( -1 );
        }
        
        // On parcourt toutes les méthodes de la classe Demo, y compris le main.
        for ( Method method : metadata.getDeclaredMethods() ) {
            
            // On vérifie la présence de notre annotation
            TestMethod annotation = method.getAnnotation( TestMethod.class );
            if ( annotation == null ) continue;
            
            // On affiche le nom de la méthode ainsi que les attributs de l'annotation.
            System.out.printf( "%s - %s - max duration = %d\n",
                    method.getName(),
                    annotation.expected() != NoExceptionExpected.class ? 
                            annotation.expected().getName() : "no exception expected",
                    annotation.timeout()
            );
        }
        
    }

}
Exemple d'utilisation de notre annotation @TestClass


Introduction à l'utilisation d'annotations Coder un mini framework de test avec les annotations