Existe-t-il quelque chose comme l'héritage d'annotation en java?

119

J'explore les annotations et suis arrivé à un point où certaines annotations semblent avoir une hiérarchie entre elles.

J'utilise des annotations pour générer du code en arrière-plan pour les cartes. Il existe différents types de cartes (donc différents codes et annotations) mais certains éléments sont communs parmi eux comme un nom.

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

Et ce serait l'annotation commune:

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

Dans l'exemple ci-dessus, je m'attendrais à ce que Move hérite de la méthode 3, mais je reçois un avertissement indiquant que l'extension n'est pas valide avec les annotations. J'essayais d'avoir une annotation qui étend une base commune mais cela ne fonctionne pas. Est-ce même possible ou est-ce juste un problème de conception?

javydreamercsw
la source
3
L'héritage des annotations semble être un incontournable pour créer un DSL basé sur des annotations. Dommage que l'héritage d'annotation ne soit pas pris en charge.
Ceki
2
Je suis d'accord, cela semble être une chose naturelle à faire. Surtout après avoir compris l'héritage sur Java, vous vous attendez à ce qu'il s'applique à tout.
javydreamercsw

Réponses:

76

Malheureusement non. Apparemment, cela a quelque chose à voir avec les programmes qui lisent les annotations sur une classe sans les charger complètement. Voir Pourquoi n'est-il pas possible d'étendre les annotations en Java?

Cependant, les types héritent des annotations de leur superclasse si ces annotations sont @Inherited .

De plus, à moins que vous n'ayez besoin de ces méthodes pour interagir, vous pouvez simplement empiler les annotations sur votre classe:

@Move
@Page
public class myAwesomeClass {}

Y a-t-il une raison qui ne fonctionnerait pas pour vous?

Andronikus
la source
1
C'est ce que je pensais mais j'essayais de simplifier les choses. Peut-être que l'application du Common à une classe abstraite ferait l'affaire ...
javydreamercsw
1
Ouais, ça avait l'air assez intelligent aussi. Bonne chance!
andronikus
67

Vous pouvez annoter votre annotation avec une annotation de base au lieu de l'héritage. Ceci est utilisé dans le framework Spring .

Pour donner un exemple

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

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

@Car
class Foo {
}

Vous pouvez ensuite vérifier si une classe est annotée avec en Vehicleutilisant AnnotationUtils de Spring :

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

Cette méthode est implémentée comme:

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

AnnotationUtilscontient également des méthodes supplémentaires pour rechercher des annotations sur des méthodes et d'autres éléments annotés. La classe Spring est également assez puissante pour rechercher dans des méthodes pontées, des proxies et d'autres cas de coin, en particulier ceux rencontrés dans Spring.

Grygoriy Gonchar
la source
13
Veuillez inclure une explication sur la manière de traiter ces annotations.
Aleksandr Dubinsky
1
Vous pouvez utiliser AnnotationUtils.findAnnotation de Spring (..), voir: docs.spring.io/spring/docs/current/javadoc-api/org/…
rgrebski
2
Lorsque l'annotation A est annotée avec une autre annotation B, et que nous annotons la classe C avec A, la classe C est traitée comme si elle était annotée à la fois avec A et B. C'est le comportement spécifique du framework Spring - AnnotationUtils.findAnnotation fait la magie ici et est utilisé pour parcourir jusqu'à trouver les annotations d'une annotation. Alors ne vous méprenez pas sur le fait qu'il s'agit du comportement par défaut de Java concernant la gestion des annotations.
qartal
Cela n'est possible que si les annotations que vous souhaitez composer ont pour cible TYPEou ANNOTATION_TYPE.
OrangeDog
7

En plus de la réponse de Grygoriys des annotations d'annotations.

Vous pouvez vérifier par exemple les méthodes pour contenir une @Qualifierannotation (ou une annotation annotée avec @Qualifier) par cette boucle:

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

Ce que vous faites essentiellement, c'est d'obtenir toutes les annotations présentes sur la méthode et de ces annotations, vous obtenez leurs types et vérifiez ces types s'ils sont annotés avec @Qualifier. Votre annotation doit également être Target.Annotation_type activée pour que cela fonctionne.

philnate
la source
En quoi est-ce différent de la réponse de Grygoriy?
Aleksandr Dubinsky
@AleksandrDubinsky: C'est juste une implémentation beaucoup plus simple sans utiliser Spring. Il ne trouve pas d'annotations annotées annotées de manière récursive, mais je suppose que ce n'est généralement pas nécessaire. J'aime la simplicité de cette solution.
Stefan Steinegger
1

Consultez https://github.com/blindpirate/annotation-magic , qui est une bibliothèque que j'ai développée lorsque j'ai eu la même question.

@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
la source