Gibt es so etwas wie Annotation Inheritance in Java?

119

Ich untersuche Anmerkungen und bin zu einem Punkt gekommen, an dem einige Anmerkungen eine Hierarchie zu haben scheinen.

Ich verwende Anmerkungen, um im Hintergrund Code für Karten zu generieren. Es gibt verschiedene Kartentypen (also unterschiedliche Codes und Anmerkungen), aber es gibt bestimmte Elemente, die wie ein Name gemeinsam sind.

@Target(value = {ElementType.TYPE})
public @interface Move extends Page{
 String method1();
 String method2();
}

Und dies wäre die übliche Anmerkung:

@Target(value = {ElementType.TYPE})
public @interface Page{
 String method3();
}

Im obigen Beispiel würde ich erwarten, dass Move Methode3 erbt, aber ich erhalte eine Warnung, dass Extend mit Anmerkungen nicht gültig ist. Ich habe versucht, eine Annotation zu haben, die eine gemeinsame Basis erweitert, aber das funktioniert nicht. Ist das überhaupt möglich oder nur ein Designproblem?

javydreamercsw
quelle
3
Die Vererbung von Anmerkungen scheint ein Muss zu sein, um ein DSL basierend auf Anmerkungen zu erstellen. Schade, dass die Vererbung von Anmerkungen nicht unterstützt wird.
Ceki
2
Ich stimme zu, scheint eine natürliche Sache zu sein. Besonders nachdem Sie die Vererbung unter Java verstanden haben, erwarten Sie, dass sie für alles gilt.
Javydreamercsw

Antworten:

76

Unglücklicherweise nicht. Anscheinend hat es etwas mit Programmen zu tun, die die Anmerkungen einer Klasse lesen, ohne sie vollständig zu laden. Siehe Warum ist es nicht möglich, Anmerkungen in Java zu erweitern?

Typen erben jedoch die Annotationen ihrer Oberklasse, wenn diese Annotationen vorhanden sind @Inherited.

Wenn Sie diese Methoden nicht für die Interaktion benötigen, können Sie auch die Anmerkungen in Ihrer Klasse stapeln:

@Move
@Page
public class myAwesomeClass {}

Gibt es einen Grund, der für Sie nicht funktionieren würde?

andronikus
quelle
1
Das dachte ich mir, aber ich versuchte die Dinge zu vereinfachen. Vielleicht würde die Anwendung des Common auf eine abstrakte Klasse den Trick tun ...
javydreamercsw
1
Ja, das sah auch ziemlich schlau aus. Viel Glück!
Andronikus
67

Sie können Ihre Anmerkung mit einer Basisanmerkung anstelle einer Vererbung versehen. Dies wird im Spring Framework verwendet .

Um ein Beispiel zu geben

@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface Vehicle {
}

@Target(value = {ElementType.TYPE})
@Vehicle
public @interface Car {
}

@Car
class Foo {
}

Anschließend können Sie überprüfen , ob eine Klasse mit Anmerkungen versehen ist Vehiclemit Spring AnnotationUtils :

Vehicle vehicleAnnotation = AnnotationUtils.findAnnotation (Foo.class, Vehicle.class);
boolean isAnnotated = vehicleAnnotation != null;

Diese Methode wird implementiert als:

public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
    return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
}

@SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
    try {
        Annotation[] anns = clazz.getDeclaredAnnotations();
        for (Annotation ann : anns) {
            if (ann.annotationType() == annotationType) {
                return (A) ann;
            }
        }
        for (Annotation ann : anns) {
            if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
                A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
                if (annotation != null) {
                    return annotation;
                }
            }
        }
    }
    catch (Exception ex) {
        handleIntrospectionFailure(clazz, ex);
        return null;
    }

    for (Class<?> ifc : clazz.getInterfaces()) {
        A annotation = findAnnotation(ifc, annotationType, visited);
        if (annotation != null) {
            return annotation;
        }
    }

    Class<?> superclass = clazz.getSuperclass();
    if (superclass == null || Object.class == superclass) {
        return null;
    }
    return findAnnotation(superclass, annotationType, visited);
}

AnnotationUtilsenthält auch zusätzliche Methoden zum Suchen nach Anmerkungen zu Methoden und anderen mit Anmerkungen versehenen Elementen. Die Spring-Klasse ist auch leistungsfähig genug, um überbrückte Methoden, Proxys und andere Eckfälle zu durchsuchen, insbesondere die in Spring angetroffenen.

Grygoriy Gonchar
quelle
13
Bitte fügen Sie eine Erklärung zur Verarbeitung solcher Anmerkungen bei.
Aleksandr Dubinsky
1
Sie können Spring's AnnotationUtils.findAnnotation (..) verwenden, siehe: docs.spring.io/spring/docs/current/javadoc-api/org/…
rgrebski
2
Wenn die Annotation A mit einer anderen Annotation B annotiert wird und wir Klasse C mit A annotieren, wird Klasse C so behandelt, als ob sie sowohl mit A als auch mit B annotiert wird. Dies ist das spezifische Verhalten von Spring Framework - AnnotationUtils.findAnnotation macht die Magie hier und wird verwendet, um nach Annotationen einer Annotation zu suchen. Verstehen Sie also nicht falsch, dass dies das Standardverhalten von Java in Bezug auf die Behandlung von Anmerkungen ist.
Qartal
Dies ist nur möglich, wenn die Anmerkungen, die Sie verfassen möchten, ein Ziel von TYPEoder haben ANNOTATION_TYPE.
OrangeDog
7

Zusätzlich zu Grygoriys Antwort auf Anmerkungen.

Sie können z. B. Methoden zum Enthalten einer @QualifierAnmerkung (oder einer mit Anmerkungen versehenen Anmerkung @Qualifier) in dieser Schleife überprüfen :

for (Annotation a : method.getAnnotations()) {
    if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
        System.out.println("found @Qualifier annotation");//found annotation having Qualifier annotation itself
    }
}

Was Sie im Grunde tun, ist, alle Anmerkungen in der Methode und von diesen Anmerkungen zu erhalten, deren Typen Sie erhalten, und diese Typen zu überprüfen, wenn sie mit @Qualifier kommentiert sind. Ihre Annotation muss auch Target.Annotation_type aktiviert sein, damit dies funktioniert.

philnate
quelle
Wie unterscheidet sich das von Grygoriys Antwort?
Aleksandr Dubinsky
@AleksandrDubinsky: Es ist nur eine viel einfachere Implementierung ohne Spring. Annotierte Annotationen werden nicht rekursiv gefunden, aber ich denke, dies ist normalerweise nicht erforderlich. Ich mag die Einfachheit dieser Lösung.
Stefan Steinegger
1

Schauen Sie sich https://github.com/blindpirate/annotation-magic an , eine Bibliothek, die ich entwickelt habe, als ich dieselbe Frage hatte.

@interface Animal {
    boolean fluffy() default false;

    String name() default "";
}

@Extends(Animal.class)
@Animal(fluffy = true)
@interface Pet {
    String name();
}

@Extends(Pet.class)
@interface Cat {
    @AliasFor("name")
    String value();
}

@Extends(Pet.class)
@interface Dog {
    String name();
}

@interface Rat {
    @AliasFor(target = Animal.class, value = "name")
    String value();
}

@Cat("Tom")
class MyClass {
    @Dog(name = "Spike")
    @Rat("Jerry")
    public void foo() {
    }
}

        Pet petAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Pet.class);
        assertEquals("Tom", petAnnotation.name());
        assertTrue(AnnotationMagic.instanceOf(petAnnotation, Animal.class));

        Animal animalAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Animal.class);
        assertTrue(animalAnnotation.fluffy());

        Method fooMethod = MyClass.class.getMethod("foo");
        List<Animal> animalAnnotations = AnnotationMagic.getAnnotationsOnMethod(fooMethod, Animal.class);
        assertEquals(Arrays.asList("Spike", "Jerry"), animalAnnotations.stream().map(Animal::name).collect(toList()));
user11949000
quelle