it-swarm.com.de

So erstellen Sie eine Anmerkungsinstanz

Ich versuche, eine Java Annotationsmagie auszuführen. Ich muss sagen, dass ich immer noch Annotationstricks nachverfolge und bestimmte Dinge für mich immer noch nicht ganz klar sind.

Also ... ich habe einige kommentierte Klassen, Methoden und Felder. Ich habe eine Methode, die Reflektion verwendet, um einige Prüfungen für die Klassen auszuführen und einige Werte in eine Klasse einzufügen. Das alles funktioniert gut.

Jetzt stehe ich jedoch vor einem Fall, in dem ich sozusagen eine Instanz einer Anmerkung benötige. Also ... Annotationen sind nicht wie normale Interfaces und Sie können keine anonyme Implementierung einer Klasse durchführen. Ich verstehe es. Ich habe mich hier in einigen Beiträgen nach ähnlichen Problemen umgesehen, aber ich kann anscheinend keine Antwort auf das finden, wonach ich suche.

Grundsätzlich möchte ich eine Annotation erhalten und instanziieren und in der Lage sein, einige ihrer Felder mit Reflektion zu setzen (ich nehme an). Gibt es überhaupt eine Möglichkeit, dies zu tun?

52
carlspring

Nun, es ist anscheinend nichts allzu Kompliziertes. Wirklich !

Wie ein Kollege hervorhob, können Sie einfach eine anonyme Instanz der Annotation (wie jede Schnittstelle) wie folgt erstellen:

MyAnnotation:

public @interface MyAnnotation
{

    String foo();

}

Code aufrufen:

class MyApp
{
    MyAnnotation getInstanceOfAnnotation(final String foo)
    {
        MyAnnotation annotation = new MyAnnotation()
        {
            @Override
            public String foo()
            {
                return foo;
            }

            @Override
            public Class<? extends Annotation> annotationType()
            {
                return MyAnnotation.class;
            }
        };

        return annotation;
    }
}

Dank an Martin Grigorov .

64
carlspring

Der in Gunnars Antwort vorgeschlagene Proxy-Ansatz ist bereits in GeAnTyRef implementiert:

Map<String, Object> annotationParameters = new HashMap<>();
annotationParameters.put("name", "someName");
MyAnnotation myAnnotation = TypeFactory.annotation(MyAnnotation.class, annotationParameters);

Dies erzeugt eine Annotation, die dem entspricht, was Sie erhalten würden:

@MyAnnotation(name = "someName")

Auf diese Weise erstellte Anmerkungsinstanzen verhalten sich identisch mit denen, die von Java normalerweise erstellt wurden, und ihre hashCode und equals wurden aus Kompatibilitätsgründen ordnungsgemäß implementiert Vorsichtsmaßnahmen wie beim direkten Instanziieren der Annotation wie in der akzeptierten Antwort: Tatsächlich verwendet JDK intern denselben Ansatz: Sun.reflect.annotation.AnnotationParser # annotationForMap .

Die Bibliothek selbst ist winzig und hat keine Abhängigkeiten.

Offenlegung: Ich bin der Entwickler hinter GeAnTyRef.

11
kaqqao

Sie können einen Annotation-Proxy wie dieser oder dieser aus dem Hibernate Validator-Projekt verwenden (Haftungsausschluss: Ich bin ein Committer des letzteren).

6
Gunnar

Sie können Sun.reflect.annotation.AnnotationParser.annotationForMap(Class, Map) verwenden:

public @interface MyAnnotation {
    String foo();
}

public class MyApp {
    public MyAnnotation getInstanceOfAnnotation(final String foo) {
        MyAnnotation annotation = AnnotationParser.annotationForMap(
            MyAnnotation.class, Collections.singletonMap("foo", "myFooResult"));
    }
}

Nachteil: Klassen aus Sun.* Können in späteren Versionen geändert werden (obwohl diese Methode seit Java 5 mit derselben Signatur) existiert) und sind nicht für alle verfügbar Java Implementierungen, siehe diese Diskussion .

Wenn das ein Problem ist: Sie könnten einen generischen Proxy mit Ihrem eigenen InvocationHandler erstellen - genau das tut AnnotationParser intern für Sie. Oder Sie verwenden Ihre eigene Implementierung von MyAnnotation wie definiert hier . In beiden Fällen sollten Sie daran denken, annotationType(), equals() und hashCode() zu implementieren, da das Ergebnis spezifisch für Java.lang.Annotation Dokumentiert ist.

5
Tobias Liefke

Eher grobe Weise mit dem Proxy-Ansatz mit Hilfe von Apache Commons AnnotationUtils

public static <A extends Annotation> A mockAnnotation(Class<A> annotationClass, Map<String, Object> properties) {
    return (A) Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class<?>[] { annotationClass }, (proxy, method, args) -> {
        Annotation annotation = (Annotation) proxy;
        String methodName = method.getName();

        switch (methodName) {
            case "toString":
                return AnnotationUtils.toString(annotation);
            case "hashCode":
                return AnnotationUtils.hashCode(annotation);
            case "equals":
                return AnnotationUtils.equals(annotation, (Annotation) args[0]);
            case "annotationType":
                return annotationClass;
            default:
                if (!properties.containsKey(methodName)) {
                    throw new NoSuchMethodException(String.format("Missing value for mocked annotation property '%s'. Pass the correct value in the 'properties' parameter", methodName));
                }
                return properties.get(methodName);
        }
    });
}

Die Typen der übergebenen Eigenschaften werden nicht mit dem in der Annotation-Schnittstelle deklarierten Typ überprüft, und fehlende Werte werden nur zur Laufzeit ermittelt.

In der Funktion ziemlich ähnlich zu dem Code, der in kaqqaos Antwort (und wahrscheinlich auch Gunnars Antwort erwähnt wird), ohne die Nachteile der Verwendung von internal Java API wie in Antwort von Tobias Liefke.

0
oujesky