Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Prochaines sessions inter-entreprises : 28-31 mars 2017 / 13-16 juin 2017
Sessions intra-entreprises sur demande.
Inscrivez-vous vite !

Les annotations @Repetables en Java 8 !

java8.jpgComme vous le savez (dans le cas contraire, foncez regarder ma conférence sur les annotations !), il est actuellement interdit de placer plusieurs fois la même annotation sur un élément donné.

Mais ça va changer avec Java 8 !

Annotations et Reflection

La technique la plus couramment utilisée pour contourner cette limitation consiste à créer une annotation wrapper (dont le nom est souvent au pluriel), contenant un tableau de l'annotation à répéter.
On peut la voir en oeuvre un peu partout dans JavaEE, par exemple avec l'annotation @JoinTable de JPA.

Si elle permet de contourner les limitations du langage, cette astuce complexifie la détection des annotations par réflexion, car il faut alors tester deux cas : celui où l'annotation apparaît seule sur l'élément, et celui où elle est portée par une annotation wrapper.

Prenons un exemple, avec l'annotation @Foo et son wrapper @Foos :

// Annotation
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
    String value();
}
 
// Wrapper
@Retention(RetentionPolicy.RUNTIME)
public @interface Foos {
    Foo[] value();
}

On peut l'utiliser comme ceci :

// Single use
@Foo("bar"),
public class Pojo {
}
 
// Multiple use with wrapper
@Foos({
    @Foo("bar"),
    @Foo("baz")
})
public class Pojo {
}

Et voici le code de détection nécessaire :

public static List<Foo> getFooAnnotations(Class<?> klass) {
        List<Foo> fooList = new ArrayList<>();
 
        // Test for single Foo
        Foo singleFoo = klass.getDeclaredAnnotation(Foo.class);
        if (singleFoo != null) {
            fooList.add(singleFoo);
        }
 
        // Test for wrapped Foos
        Foos fooWrapper = klass.getDeclaredAnnotation(Foos.class);
        if (fooWrapper != null) {
            Foo[] foos = fooWrapper.value();
            if (foos != null) {
                for (Foo foo : foos) {
                    fooList.add(foo);
                }
            }
        }
 
        return fooList;
    }

Avouons-le, ce n'est pas très pratique. Et surtout, c'est pénible de devoir écrire autant de code boiler-plate pour contourner les limitations du langage...
Mais heureusement, Java 8, qui devrait (enfin !) sortir mi-2014, nous promet quelques améliorations dans ce domaine.

Java 8 : vers des annotations répétables

Une nouvelle méta-annotation...

Java 8 introduit une nouvelle méta-annotation : @Repeatable. Placée sur une annotation, elle indique que celle-ci peut apparaître plusieurs fois sur un même élément.

@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Repeatable {
 
    Class<? extends Annotation> value();
 
}

Java serait-il devenu plus permissif ?
En réalité, pas du tout. Il ne s'agit que d'un simple "sucre syntaxique". Si le compilateur détecte l'utilisation multiple d'une annotation marquée comme @Repeatable, il insère automatiquement un wrapper du type précisé en paramètre de l'annotation @Repeatable.

Reprenons notre exemple, et déclarons que l'annotation @Foo est @Repeatable via un wrapper @Foos :

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Foos.class)
public @interface Foo {
    String value();
}

Il est alors possible d'écrire directement :

@Foo("bar")
@Foo("baz")
public class Pojo {
}

Et le compilateur génére, comme précédemment :

@Foos({
    @Foo("bar"),
    @Foo("baz")
})
public class Pojo {
}

Pratique, mais pas vraiment révolutionnaire.

...et une nouvelle API !

En revanche, du côté API, on note l'apparition sur la classe Class de deux nouvelles méthodes très pratiques : getAnnotationsByType() et getDeclaredAnnotationsByType() (on reconnaît ici l'habituel pattern getX() / getDeclaredX()).

Ces nouvelles méthodes fonctionnent exactement comme les "anciennes", mais sont en plus capables de prendre en compte l'aspect @Repeatable des annotations et de les détecter de manière transparente au sein de leur wrapper.

La méthode de détection vue plus haut peut alors être réécrite plus simplement :

public static List<Foo> getFooAnnotationsByType(Class<?> klass) {
    Foo[] foos = klass.getDeclaredAnnotationsByType(Foo.class);
    return Arrays.asList(foos);
}

Evidemment, cela ne fonctionne que si les annotations répétables sont effectivement annotées @Repeatable... Mais il ne fait aucun doute que les principaux frameworks (JavaEE, Spring, Hibernate...) tireront parti de cette nouvelle fonctionnalité très rapidement après la sortie de Java 8 !

Conclusion

Mine de rien, Java 8 apportera de petites améliorations pratiques, à côté des fonctionnalités blockbusters comme les Lambda ou la nouvelle API Date&Time.

Le problème de la répétabilité des annotations n'est toujours pas réellement pris en compte par la grammaire du langage, mais les API permettent de contourner cette limitation avec plus ou moins de facilité.

Pour terminer, et dans un souci d'exhaustivité, notons également l'arrivée de la méta-annotation @Native, pouvant servir lors de l'interfaçage avec du code non-Java. Si c'est utile à quelqu'un, il gagne un sandwich au thon et toute mon admiration.


Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.