Procesamiento de anotaciones de Java 6: obtener una clase de una anotación

6 minutos de lectura

Tengo una anotación personalizada llamada @Pojo que uso para la generación automática de documentación 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();
}

Yo lo uso así:

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

para anotar un método de recurso para que el procesador de anotaciones pueda generar automáticamente una página wiki que describa el recurso y el tipo que espera.

Necesito leer el valor de la value campo en un procesador de anotaciones, pero recibo un error de tiempo de ejecución.

En el código fuente de mi procesador tengo las siguientes líneas:

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

pero la clase real no está disponible para el procesador. creo que necesito un javax.lang.model.type.TypeMirror en cambio, como un sustituto de la clase real. No estoy seguro de cómo conseguir uno.

El error que estoy recibiendo es:

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

El Appointment es una clase mencionada en uno de mis @Pojo anotación.

Desafortunadamente, los documentos y/o tutoriales sobre el procesamiento de anotaciones en Java parecen escasos. Intenté googlear.

  • No mucha gente hace este tipo de preguntas, pero para los que vivimos en el (Java) Tartarus, el apoyo de los demás resulta muy útil.

    – Ordiel

    25 de enero de 2015 a las 6:59


Avatar de usuario de Dave Dopson
david dopson

Vine aquí para hacer EXACTAMENTE la misma pregunta. … y encontré lo mismo enlace del blog publicado por Ralph.

Es un artículo largo, pero muy rico. Resumen de la historia: hay dos formas de hacerlo, la forma fácil y la forma “más correcta”.

Esta es la manera fácil:

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

La otra forma más tediosa (sin excepciones):

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();
    }
}

Por supuesto, una vez que obtienes un TypeMirror, (al menos en mi experiencia) casi siempre quieres un TypeElement en su lugar:

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

… esa última pequeña parte no obvia me tomó una hora de tirar del pelo antes de resolverlo por primera vez. Estos procesadores de anotaciones en realidad no son tan difíciles de escribir, las API son súper confusas al principio y alucinantemente detalladas. Estoy tentado a publicar una clase de ayuda que hace que todas las operaciones básicas sean obvias… pero esa es una historia para otro día (envíame un mensaje si lo deseas).

  • Estoy atrapado aquí también. ¿Podría publicar una idea general con su ayudante si hizo algo, por favor?

    – gorodechnyj

    29 de mayo de 2014 a las 10:23

  • @gorodechnyj: aquí está el código que estaba escribiendo en ese momento: github.com/ddopson/EpicFramework/blob/uiv2/EpicBuilder/src/com/… Siéntase libre de cavar a través de él y robar generosamente.

    – Dave Dopson

    5 de junio de 2014 a las 18:33

  • Creo que +1 no es suficiente para esta respuesta. Debería ser +10.

    – Mohamed El-Beltagy

    30 de julio de 2018 a las 2:42

  • @MohamedEl-Beltagy – ¡Muchas gracias por tus amables palabras!

    – Dave Dopson

    31 de julio de 2018 a las 16:36

  • Esto es genial. Solo puedo estar de acuerdo con @MohamedEl-Beltagy. Aquí hay otro gran artículo sobre el mismo tema para cualquier persona interesada. hauchee.blogspot.com/2015/12/…

    – Lukasz

    31 de enero de 2019 a las 19:22

Has leído este artículo: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ ?

Allí, el truco es usar getAnnotation() y capturar la MirroredTypeException. Sorprendentemente, la excepción proporciona el TypeMirror de la clase requerida.

No sé si esta es una buena solución, pero lo es. En mi opinión personal, intentaría obtener el tipo detrás de MirroredType, pero no sé si esto es posible.

  • Eso puede ser el truco, aunque me viene a la mente “cinta adhesiva y goma de mascar”. Por cierto, genial nombre de usuario :-).

    – Ralph

    7 oct 2011 a las 13:48

Mi respuesta es básicamente la misma que la de Dave Dopson, solo que el código cambió un poco:

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;
}

Su código usado TypeElement como el tipo para el primer argumento de getAnnotationMirror(), limitando el uso del método solo para clases. Cambiando eso a Element lo hace utilizable para cualquier elemento anotado. Usando una implementación basada en flujos en lugar del original for bucle es sólo una cuestión de gusto.

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 usó Element.getElementValues() para obtener los valores de la anotación, pero esto devuelve solo aquellos valores que se aplicaron explícitamente a la anotación concreta. Se omitirán los valores predeterminados. El método Elements.getElementValuesWithDefaults() devolverá también los valores predeterminados (como ya habrá adivinado por el nombre).

Este es mi truco:

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;
  }

}

Entonces puedo usar con cualquier valor de anotación:

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

Ejemplo

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

Uso en el procesador de anotaciones

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

Probado con JDK 1.8.0_192

¿Ha sido útil esta solución?