Czy jest coś takiego jak dziedziczenie adnotacji w Javie?

119

Badam adnotacje i doszedłem do punktu, w którym niektóre adnotacje wydają się mieć hierarchię między nimi.

Używam adnotacji do generowania kodu w tle dla kart. Istnieją różne typy kart (a więc inny kod i adnotacje), ale są pewne elementy, które są między nimi wspólne, takie jak nazwa.

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

I to byłaby wspólna adnotacja:

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

W powyższym przykładzie spodziewałbym się, że Move odziedziczy method3, ale pojawia się ostrzeżenie, że extends nie jest poprawne w przypadku adnotacji. Próbowałem, aby adnotacja była rozszerzeniem wspólnej bazy, ale to nie działa. Czy to w ogóle możliwe, czy tylko kwestia projektu?

javydreamercsw
źródło
3
Dziedziczenie adnotacji wydaje się być niezbędne do tworzenia DSL na podstawie adnotacji. Szkoda, że ​​dziedziczenie adnotacji nie jest obsługiwane.
Ceki
2
Zgadzam się, wydaje się naturalne. Zwłaszcza po zrozumieniu dziedziczenia w Javie można oczekiwać, że będzie miało zastosowanie do wszystkiego.
javydreamercsw

Odpowiedzi:

76

Niestety nie. Najwyraźniej ma to coś wspólnego z programami, które odczytują adnotacje w klasie bez ich ładowania. Zobacz Dlaczego nie można rozszerzać adnotacji w języku Java?

Jednak typy dziedziczą adnotacje swojej nadklasy, jeśli te adnotacje są @Inherited.

Ponadto, jeśli nie potrzebujesz tych metod do interakcji, możesz po prostu ułożyć adnotacje w swojej klasie:

@Move
@Page
public class myAwesomeClass {}

Czy jest jakiś powód, dla którego nie zadziała?

andronikus
źródło
1
Tak myślałem, ale starałem się wszystko uprościć. Może zastosowanie tej wspólnej do klasy abstrakcyjnej
załatwi sprawę
1
Tak, to też wyglądało całkiem sprytnie. Powodzenia!
andronikus
67

Możesz dodać adnotację do adnotacji bazowej zamiast dziedziczenia. Jest to używane w ramach Spring .

Dać przykład

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

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

@Car
class Foo {
}

Następnie możesz sprawdzić, czy klasa jest opatrzona adnotacją za Vehiclepomocą AnnotationUtils Springa :

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

Ta metoda jest implementowana jako:

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

AnnotationUtilszawiera również dodatkowe metody wyszukiwania adnotacji w metodach i innych elementach z adnotacjami. Klasa Spring jest również wystarczająco potężna, aby przeszukiwać metody mostkowane, serwery proxy i inne przypadki narożne, szczególnie te napotkane w Spring.

Grygoriy Gonchar
źródło
13
Dołącz wyjaśnienie, jak należy przetwarzać takie adnotacje.
Aleksandr Dubinsky
1
Możesz użyć Spring's AnnotationUtils.findAnnotation (..), zobacz: docs.spring.io/spring/docs/current/javadoc-api/org/…
rgrebski
2
Kiedy adnotacja A jest adnotowana inną adnotacją B, a my adnotujemy klasę C za pomocą A, klasa C jest traktowana tak, jakby była opatrzona adnotacją zarówno z adnotacją A, jak i B. Jest to specyficzne zachowanie platformy Spring - AnnotationUtils.findAnnotation robi magię tutaj i służy do przechodzenia w górę w celu znalezienia adnotacji adnotacji. Więc nie zrozumcie źle, że jest to domyślne zachowanie Javy dotyczące obsługi adnotacji.
qartal
Jest to możliwe tylko wtedy, gdy adnotacje, które chcesz utworzyć, mają cel TYPElub ANNOTATION_TYPE.
OrangeDog
7

Oprócz odpowiedzi Grygoriys z adnotacjami.

Możesz sprawdzić np. Metody zawierające @Qualifieradnotację (lub adnotację opatrzoną adnotacją @Qualifier) za pomocą tej pętli:

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

Zasadniczo robisz to, aby uzyskać wszystkie adnotacje obecne w metodzie, a z tych adnotacji otrzymasz ich typy i sprawdź te typy, jeśli są adnotowane @Qualifier. Aby ta adnotacja działała, również musi być włączona opcja Target.Annotation_type.

filnate
źródło
Czym to się różni od odpowiedzi Grygoriy?
Aleksandr Dubinsky
@AleksandrDubinsky: To po prostu dużo prostsza implementacja bez użycia Springa. Nie znajduje rekursywnie adnotacji z adnotacjami, ale myślę, że zwykle nie jest to wymagane. Podoba mi się prostota tego rozwiązania.
Stefan Steinegger
1

Sprawdź https://github.com/blindpirate/annotation-magic , czyli bibliotekę, którą opracowałem, gdy miałem to samo pytanie.

@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
źródło