Devoxx 2012 – JSR 308 – Annotations On Java Types

Java 8 est attendu de pied ferme : ajout des lambdas, invoke dynamic, suppression de la Perm Gem… mais il n’y aura pas que ça dans cette nouvelle version.

En effet, côté annotations, il y a aussi des innovations : possibilité d’ajouter une annotation sur les types d’objets (JSR 308 – Annotations On Java Types) ; possibilité de répéter une annotation sur une déclaration (JEP 120 – Repeatiting Annotations) ; portage de l’API “javax.lang.model” au runtime pour qu’elle ne soit pas disponible uniquement à la compilation (JEP 119 – javax.lang.model Implementation Backed by Core Reflection).

Tout ceci est présenté par Joel Borggrén-Franck, qui travaille actuellement sur le compilateur Java en général et plus spécifiquement sur les fonctionnalités liées aux annotations.

JSR 308 – Annotations On Java Types

Actuellement, il n’est pas possible de mettre une annotation sur un argument de généricité d’un objet (exemple : List<MonType>), lors d’un cast, etc. Via cette JSR, nous pourrons annoter des types, en plus de la possibilité déjà présente d’annoter des variables, class et méthodes.


    new @Interned MyObject();

    myString = (@NonNull String) myObject;

    class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }

    void monitorTemperature() throws @Critical TemperatureException { ... }

    

L’intérêt est de pouvoir lancer des vérificateurs (“checkers“) sur les types associés à ces annotations. Annoter un type avec l’annotation @NonNull puis utiliser le checker correspondant pour détecter qu’aucune variable du type choisi ne puisse être nulle lors de la compilation du code. Un bon moyen pour limiter fortement, voire éliminer la découverte d’un NullPointerException dans son code en production.

Vous pouvez faire un tour sur le site du framework “checkers” pour découvrir la liste d’autres checkers.

Il faudra tout de même faire attention à ne pas abuser des annotations, au risque de se retrouver avec du code bien plus chargé en annotations qu’en code métier : trop d’annotations tue l’annotation.

JEP 120 – Repeating Annotations

Aujourd’hui, il n’est pas possible d’annoter une déclaration avec seulement une annotation d’un même type. On se retrouve donc avec des annotations qui jouent le rôle de conteneur d’annotations (dans l’exemple ci-dessous : @Schedules). Et c’est via ces dernières annotations qu’on peut répéter une (autre) annotation (dans l’exemple ci-dessous : @Schedule).


@Schedules ({

    @Schedule(dayOfMonth="Last"),

    @Schedule(dayOfWeek="Fri", hour="23")

})

public void doPeriodicCleanup() { ... }

Avec Java 8, il sera possible de s’affranchir de cette contrainte, et d’écrire directement une suite d’annotations. Le code sera moins verbeux et donc plus lisible. C’est donc une très bonne nouvelle en tant qu’utilisateur de ce genre d’annotations.


@Schedule(dayOfMonth="Last"),

@Schedule(dayOfWeek="Fri", hour="23")

public void doPeriodicCleanup() { … }

Pour des raisons de rétrocompatibilité après la compilation, les annotations répétées seront toujours dans leurs “conteneurs” d’annotations. C’est le compilateur qui se chargera d’ajouter l’annotation manquante, dans notre exemple @Schedules.

Si vous êtes amené à créer ce type d’annotation, la mise en œuvre est aussi très simple. Deux nouvelles annotations ont été introduites : @ContainedBy & @ContainerFor. Il faudra ajouter @ContainerFor sur l’annotation qui va contenir les annotations à répéter.


@ContainerFor(Schedule.class)

public @interface Schedules {

    Schedule[] value;

}

@ContainedBy devra quant à elle être positionnée sur les annotations répétables. C’est grâce à cette annotation que le compilateur pourra savoir avec quel container il devra encapsuler les annotations répétées.


@ContainedBy(Schedules.class)

public @interface Schedule { … }

Comme on peut le voir, les modifications pour qu’un “container” d’annotations devienne facultatif pour l’utilisateur sont mineures. Les frameworks utilisant pléthore d’annotations prendront sûrement rapidement en compte ces nouveautés.

Une question se pose maintenant : Comment obtenir la liste des annotations d’un même type sur une méthode ? Actuellement, ce n’est pas possible. Il existe bien Class#getAnnotation(Class<A> annotationClass) mais elle ne va permettre de récupérer qu’une seule annotation de notre type, parmi notre suite d’annotations. De nouvelles méthodes de réflexion vont donc apparaître, cf tableau ci-dessous.

Type de retour Nom de la méthode Comportement
<T extends Annotation> T[] getAnnotations(Class<T>) Retourne toutes les annotations de type T présentes directement ou par héritage
<T extends Annotation> T[] getDeclaredAnnotations(Class<T>) Retourne toutes les annotations de type T présentes directement
<T extends Annotation> T getDeclaredAnnotation(Class<T>) Retourne une annotation de type T présentes directement (Méthode qui aurait déjà dû exister, hors du cadre de cette JEP)

Les annotations répétables ne sont clairement pas une aussi grande évolution que les lambdas, mais ce genre de petit sucre syntaxique pour simplifier notre vie de développeur fait tout de même plaisir à voir (et à utiliser,  j’espère ! )

JEP 119 – javax.lang.model Implementation Backed by Core Reflection

javax.lang.model est une API permettant de dialoguer avec le code source Java, à un niveau plus haut que l’API de réflexion Java, qui elle, ne peut plus accéder aux éléments supprimés ou cachés par la compilation. Le principal utilisateur de cette API est pour l’instant le gestionnaire d’annotations à la compilation. (D’ailleurs, en passant : apt ne sera plus présent dans java 8). L’intention de la JEP 119 est d’offrir l’accès à cet API au runtime et non plus uniquement à la compilation.

En quoi cette API est plus intéressante que l’API de réflexion ?

La réponse est qu’elle a accès à plus d’éléments que la réflexion, qu’elle est capable de résoudre simplement des problèmes complexes à résoudre avec l’API de réflexion.

Joël montre cela avec un exemple :

Une classe A possède une méthode M. Une interface B avec la signature d’une méthode aussi nommée M est implémentée par une classe C. Cette classe C hérite également de A. (Vous suivez toujours ?)


class A { public void M() {} }

interface B { void M(); }

class C extends A implements B { }

Comment savoir, via réflexion, si la méthode M de A est une surcharge de M de B dans la classe C ? Joël dit que c’est compliqué… (et je veux bien le croire)

Pour résoudre ce problème, l’API javax.lang.model va exposer une factory permettant de récupérer un miroir de sa classe ayant un sens pour le package javax.lang.model.


TypeElement       CoreReflectionFactory.createMirror(Class<t> clazz);

ExecutableElement CoreReflectionFactory.createMirror(Method method);

C’est à partir de ces miroirs que l’on pourra interroger et obtenir les informations qui nous intéressent. Comme savoir si la méthode M de A surcharge la méthode M de B dans la classe C. (et tout cela, au runtime ! )


// éléments est de type javax.lang.model.util.Elements

boolean isOverrided = elements.overrides(mirroirMethodeMdeA, mirroirMethodeMdeB, mirroirClassC));

// affiche true

System.out.println("overrided ? "+isOverrided);

L’API résout des problèmes qu’on ne rencontre pas forcément tous les jours – à moins d’utiliser de la réflexion dans son projet – mais toujours est-il, qu’avec cette API et son lot de visiteurs, il sera plus simple de parcourir la définition des classes.

Par contre, attention, le travail sur cette JEP n’est pas encore fini ! Des choses pourraient encore changer…

Nombre de vue : 108

AJOUTER UN COMMENTAIRE