web-dev-qa-db-fra.com

Traitement d'annotation Java 6 - Obtenir une classe à partir d'une annotation

J'ai une annotation personnalisée appelée @Pojo que j'utilise pour la génération automatique de documentation wiki:

package com.example.annotations;

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

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Pojo {
    Class<?> value();
}

Je l'utilise comme ça:

@Pojo(com.example.restserver.model.appointment.Appointment.class)

d'annoter une méthode de ressource afin que le processeur d'annotation puisse générer automatiquement une page wiki décrivant la ressource et le type qu'il attend.

Je dois lire la valeur du champ value dans un processeur d'annotation, mais j'obtiens une erreur d'exécution.

Dans le code source de mon processeur, j'ai les lignes suivantes:

final Pojo pojo = element.getAnnotation(Pojo.class);
// ...
final Class<?> pojoJavaClass = pojo.value();

mais la classe réelle n'est pas disponible pour le processeur. Je pense que j'ai besoin d'un javax.lang.model.type.TypeMirror à la place en tant que substitut de la vraie classe. Je ne sais pas comment en obtenir un.

L'erreur que je reçois est la suivante:

javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror com.example.restserver.model.appointment.Appointment

Appointment est une classe mentionnée dans l'une de mes annotations @Pojo.

Malheureusement, les documents et/ou tutoriels sur le traitement des annotations Java semblent rares. Essayé googler.

40
Ralph

Avez-vous lu cet article: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ ?

Le truc consiste à utiliser réellement getAnnotation () et à récupérer l’exception MirroredTypeException. Étonnamment, l’exception fournit alors le TypeMirror de la classe requise.

Je ne sais pas si c'est une bonne solution, mais c'est la même chose. Dans mon opinion personnelle, j'essayerais de trouver le type derrière le MirroredType, mais je ne sais pas si cela est possible.

24
Ralph

Je suis venu ici pour poser la même question exacte. ... et a trouvé le même lien de blog posté par Ralph.

C'est un long article, mais très riche. Résumé de l'histoire - Il y a deux façons de le faire, la plus simple et la plus simple.

C'est le moyen le plus simple:

private static TypeMirror getMyValue1(MyAnnotation annotation) {
    try
    {
        annotation.myValue(); // this should throw
    }
    catch( MirroredTypeException mte )
    {
        return mte.getTypeMirror();
    }
    return null; // can this ever happen ??
}

L'autre façon plus fastidieuse (sans exceptions):

private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
    String clazzName = clazz.getName();
    for(AnnotationMirror m : typeElement.getAnnotationMirrors()) {
        if(m.getAnnotationType().toString().equals(clazzName)) {
            return m;
        }
    }
    return null;
}

private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
    for(Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet() ) {
        if(entry.getKey().getSimpleName().toString().equals(key)) {
            return entry.getValue();
        }
    }
    return null;
}


public TypeMirror getMyValue2(TypeElement foo) {
    AnnotationMirror am = getAnnotationMirror(foo, MyAnnotation.class);
    if(am == null) {
        return null;
    }
    AnnotationValue av = getAnnotationValue(am, "myValue");
    if(av == null) {
        return null;
    } else {
        return (TypeMirror)av.getValue();
    }
}

Bien sûr, une fois que vous avez reçu un TypeMirror, vous (du moins d'après mon expérience) vous préférez toujours un TypeElement:

private TypeElement asTypeElement(TypeMirror typeMirror) {
    Types TypeUtils = this.processingEnv.getTypeUtils();
    return (TypeElement)TypeUtils.asElement(typeMirror);
}

... ce dernier petit peu évident m'a pris une heure de tirage de cheveux avant que je l'ai trié la première fois. Ces processeurs d'annotations ne sont en fait pas si difficiles à écrire, les API sont simplement très déroutantes au début et extrêmement verbeuses. Je suis tenté de mettre en place une classe d'aide qui rend toutes les opérations de base évidentes ... mais c'est une histoire pour un autre jour (msg moi si vous le voulez).

39
Dave Dopson

Ma réponse est fondamentalement la même que celle de Dave Dopson, seul le code a été légèrement modifié:

public default Optional<? extends AnnotationMirror> getAnnotationMirror( final Element element, final Class<? extends Annotation> annotationClass )
{
    final var annotationClassName = annotationClass.getName();
    final Optional<? extends AnnotationMirror> retValue = element.getAnnotationMirrors().stream()
        .filter( m -> m.getAnnotationType().toString().equals( annotationClassName ) )
        .findFirst();

    return retValue;
}

Son code utilisait TypeElement comme type du premier argument de getAnnotationMirror(), limitant l'utilisation de la méthode aux classes uniquement. Changer cela en Element le rend utilisable pour tout élément annoté. Utiliser une implémentation basée sur des flux plutôt que sur la boucle for originale est juste une question de goût.

public default Optional<? extends AnnotationValue> getAnnotationValue( final AnnotationMirror annotationMirror, final String name )
{
    final Elements elementUtils = this.processingEnv.getElementUtils();
    final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = elementUtils.getElementValuesWithDefaults( annotationMirror );
    final Optional<? extends AnnotationValue> retValue = elementValues.keySet().stream()
        .filter( k -> k.getSimpleName().toString().equals( name ) )
        .map( k -> elementValues.get( k ) )
        .findAny();

    return retValue;
}

Dave a utilisé Element.getElementValues() pour obtenir les valeurs d'annotation, mais cela ne renvoie que les valeurs explicitement appliquées à l'annotation concrète. Les valeurs par défaut seront omises. La méthode Elements.getElementValuesWithDefaults() renverra également les valeurs par défaut (comme vous l'avez peut-être déjà deviné par son nom).

0
tquadrat

C'est mon truc:

public class APUtils {

  @FunctionalInterface
  public interface GetClassValue {
    void execute() throws MirroredTypeException, MirroredTypesException;
  }

  public static List<? extends TypeMirror> getTypeMirrorFromAnnotationValue(GetClassValue c) {
    try {
      c.execute();
    }
    catch(MirroredTypesException ex) {
      return ex.getTypeMirrors();
    }
    return null;
  }

}

Ensuite, je peux utiliser n'importe quelle valeur d'annotation:

public @interface MyAnnotationType {
  public Class<?>[] value() default {};
}

Exemple

@MyAnnotationType(String.class)
public class Test { ... }

Utilisation en processeur d'annotation

MyAnnotationType a = element.getAnnotation(MyAnnotationType.class);
List<? extends TypeMirror> types = APUtils.getTypeMirrorFromAnnotationValue(() -> a.value());

Testé avec JDK 1.8.0_192

0
mnesarco