Esiste qualcosa come Annotation Inheritance in java?


119

Sto esplorando le annotazioni e sono arrivato a un punto in cui alcune annotazioni sembrano avere una gerarchia tra di loro.

Sto usando le annotazioni per generare codice in background per le carte. Esistono diversi tipi di carte (quindi codice e annotazioni diversi) ma ci sono alcuni elementi comuni tra loro come un nome.

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

E questa sarebbe l'annotazione comune:

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

Nell'esempio sopra, mi aspetto che Move to inherit method3 ma ricevo un avviso che dice che extends non è valido con le annotazioni. Stavo cercando di fare in modo che un'annotazione estendesse una base comune ma non funziona. È anche possibile o è solo un problema di design?


3
L'ereditarietà delle annotazioni sembra un must per la creazione di un DSL basato sulle annotazioni. È un vero peccato che l'ereditarietà delle annotazioni non sia supportata.
Ceki

2
Sono d'accordo, sembra una cosa naturale da fare. Soprattutto dopo aver compreso l'ereditarietà su Java, ti aspetti che si applichi a tutto.
javydreamercsw

Risposte:


76

Sfortunatamente no. Apparentemente ha qualcosa a che fare con programmi che leggono le annotazioni su una classe senza caricarle del tutto. Vedi Perché non è possibile estendere le annotazioni in Java?

Tuttavia, i tipi ereditano le annotazioni della loro superclasse se tali annotazioni lo sono @Inherited.

Inoltre, a meno che tu non abbia bisogno di questi metodi per interagire, potresti semplicemente impilare le annotazioni sulla tua classe:

@Move
@Page
public class myAwesomeClass {}

C'è qualche motivo per cui non funzionerebbe per te?


1
Era quello che pensavo ma stavo cercando di semplificare le cose. Forse applicare quello comune a una classe astratta farebbe il trucco ...
javydreamercsw

1
Sì, anche quello sembrava abbastanza intelligente. In bocca al lupo!
andronikus

67

Puoi annotare la tua annotazione con un'annotazione di base invece che con l'ereditarietà. Viene utilizzato nel framework Spring .

Per fare un esempio

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

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

@Car
class Foo {
}

È quindi possibile verificare se una classe è annotata Vehicleutilizzando AnnotationUtils di Spring :

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

Questo metodo è implementato come:

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

AnnotationUtilscontiene anche metodi aggiuntivi per la ricerca di annotazioni su metodi e altri elementi annotati. La classe Spring è anche abbastanza potente da cercare attraverso metodi con bridge, proxy e altri casi d'angolo, in particolare quelli incontrati in Spring.


13
Includere una spiegazione su come elaborare tali annotazioni.
Aleksandr Dubinsky

1
È possibile utilizzare AnnotationUtils.findAnnotation (..) di Spring, vedere: docs.spring.io/spring/docs/current/javadoc-api/org/…
rgrebski

2
Quando l'annotazione A viene annotata con un'altra annotazione B e noi annotiamo la classe C con A, la classe C viene trattata come se fosse annotata sia con A che con B. Questo è il comportamento specifico del framework Spring - AnnotationUtils.findAnnotation fa la magia qui e viene utilizzato per spostarsi verso l'alto per trovare le annotazioni di un'annotazione. Quindi non fraintendere che questo è il comportamento predefinito di Java per quanto riguarda la gestione delle annotazioni.
qartal

Ciò è possibile solo se le annotazioni che desideri comporre hanno un obiettivo di TYPEo ANNOTATION_TYPE.
OrangeDog

7

Oltre alla risposta di Grygoriys sull'annotazione delle annotazioni.

Puoi controllare ad esempio i metodi per contenere @Qualifierun'annotazione (o un'annotazione annotata con @Qualifier) da questo ciclo:

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

Quello che stai facendo fondamentalmente è ottenere tutte le annotazioni presenti sul metodo e di quelle annotazioni ottieni i loro tipi e controlla quei tipi se sono annotati con @Qualifier. Anche la tua annotazione deve essere Target.Annotation_type abilitata affinché funzioni.


In che modo è diverso dalla risposta di Grygoriy?
Aleksandr Dubinsky

@AleksandrDubinsky: È solo un'implementazione molto più semplice senza usare Spring. Non trova in modo ricorsivo le annotazioni annotate annotate, ma immagino che di solito non sia richiesto. Mi piace la semplicità di questa soluzione.
Stefan Steinegger

1

Dai un'occhiata a https://github.com/blindpirate/annotation-magic , che è una libreria che ho sviluppato quando ho avuto la stessa domanda.

@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()));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.