Java 6-Annotationsverarbeitung – Abrufen einer Klasse aus einer Annotation

Lesezeit: 5 Minuten

Ich habe eine benutzerdefinierte Anmerkung namens @Pojo, die ich für die automatische Generierung der Wiki-Dokumentation verwende:

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

Ich benutze es so:

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

eine Ressourcenmethode zu kommentieren, damit der Anmerkungsprozessor automatisch eine Wiki-Seite generieren kann, die die erwartete Ressource und den erwarteten Typ beschreibt.

Ich muss den Wert von lesen value Feld in einem Anmerkungsprozessor, aber ich erhalte einen Laufzeitfehler.

Im Quellcode für meinen Prozessor habe ich folgende Zeilen:

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

aber die eigentliche Klasse steht dem Prozessor nicht zur Verfügung. Ich glaube, ich brauche eine javax.lang.model.type.TypeMirror stattdessen als Ersatz für die echte Klasse. Ich bin mir nicht sicher, wie man einen bekommt.

Der Fehler, den ich bekomme, ist:

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

Der Appointment ist eine Klasse, die in einem meiner erwähnt wird @Pojo Anmerkung.

Leider scheinen Dokumente und/oder Tutorials zur Verarbeitung von Java-Annotationen rar zu sein. Habe versucht zu googeln.

  • Nicht viele Leute stellen diese Art von Fragen, aber für diejenigen von uns, die im (Java-)Tartarus leben, erweist sich die Unterstützung anderer als sehr nützlich

    – Ordel

    25. Januar 2015 um 6:59 Uhr


Benutzeravatar von Dave Dopson
David Dopson

Ich bin hierher gekommen, um genau die gleiche Frage zu stellen. … und das gleiche gefunden Blog-Link gepostet von Ralf.

Es ist ein langer Artikel, aber sehr reichhaltig. Zusammenfassung der Geschichte – es gibt zwei Möglichkeiten, es zu tun, den einfachen Weg und den „richtigeren“ Weg.

Dies ist der einfache Weg:

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

Der andere mühsamere Weg (ohne Ausnahmen):

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

Sobald Sie einen TypeMirror erhalten haben, möchten Sie (zumindest meiner Erfahrung nach) natürlich immer ein TypeElement stattdessen:

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

… dieser letzte kleine, nicht offensichtliche Teil hat mich eine Stunde lang an den Haaren gezogen, bevor ich ihn das erste Mal aussortiert habe. Diese Anmerkungsprozessoren sind eigentlich gar nicht so schwer zu schreiben, die APIs sind anfangs nur super verwirrend und unglaublich ausführlich. Ich bin versucht, eine Hilfsklasse herauszubringen, die alle grundlegenden Operationen offensichtlich macht … aber das ist eine Geschichte für einen anderen Tag (sg mir, wenn Sie es wollen).

  • Ich stecke auch hier fest. Könnten Sie bitte einen Kern mit Ihrem Helfer veröffentlichen, wenn Sie einen gemacht haben?

    – gorodetschnyj

    29. Mai 2014 um 10:23 Uhr

  • @gorodechnyj – hier ist der Code, den ich damals geschrieben habe: github.com/ddopson/EpicFramework/blob/uiv2/EpicBuilder/src/com/… Fühlen Sie sich frei, es durchzugraben und großzügig zu stehlen.

    – David Dopson

    5. Juni 2014 um 18:33 Uhr

  • Ich denke, dass +1 für diese Antwort nicht ausreicht. Es sollte +10 sein.

    – Mohamed El-Beltagy

    30. Juli 2018 um 2:42 Uhr

  • @MohamedEl-Beltagy – vielen Dank für deine freundlichen Worte!

    – David Dopson

    31. Juli 2018 um 16:36 Uhr

  • Das ist tolles Zeug. Ich kann @MohamedEl-Beltagy nur zustimmen. Hier ist ein weiterer großartiger Artikel zum gleichen Thema für alle Interessierten. hauchee.blogspot.com/2015/12/…

    – Lukasz

    31. Januar 2019 um 19:22 Uhr

Hast du diesen Artikel gelesen: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ ?

Dort besteht der Trick darin, tatsächlich getAnnotation() zu verwenden und die MirroredTypeException abzufangen. Überraschenderweise liefert die Ausnahme dann den TypeMirror der benötigten Klasse.

Ich weiß nicht, ob das eine gute Lösung ist, aber es ist eine. Meiner persönlichen Meinung nach würde ich versuchen, den Typ hinter den MirroredType zu bekommen, aber ich weiß nicht, ob das möglich ist.

  • Das mag ausreichen, obwohl mir “Klebeband und Kaugummi” in den Sinn kommen. Übrigens, cooler Benutzername :-).

    – Ralf

    7. Oktober 2011 um 13:48 Uhr

Meine Antwort ist im Grunde die gleiche wie die von Dave Dopson, nur der Code wurde etwas geändert:

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

Sein Code verwendet TypeElement als Typ für das erste Argument von getAnnotationMirror(), wodurch die Verwendung der Methode nur auf Klassen beschränkt wird. Ändern Sie das zu Element macht es für jedes annotierte Element verwendbar. Verwendung einer auf Streams basierenden Implementierung anstelle des Originals for Schleife ist reine Geschmackssache.

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

David verwendet Element.getElementValues() um die Annotationswerte zu erhalten, aber dies gibt nur die Werte zurück, die explizit auf die konkrete Annotation angewendet wurden. Die Voreinstellungen werden weggelassen. Die Methode Elements.getElementValuesWithDefaults() gibt auch die Standardwerte zurück (wie Sie vielleicht schon aus dem Namen erraten haben).

Das ist mein Trick:

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

}

Dann kann ich mit jedem Anmerkungswert verwenden:

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

Beispiel

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

Verwendung im Anmerkungsprozessor

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

Getestet mit JDK 1.8.0_192

1449710cookie-checkJava 6-Annotationsverarbeitung – Abrufen einer Klasse aus einer Annotation

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy