Existe algo como herança de anotação em java?


119

Estou explorando anotações e cheguei a um ponto em que algumas anotações parecem ter uma hierarquia entre elas.

Estou usando anotações para gerar código em segundo plano para cartões. Existem diferentes tipos de cartões (portanto, códigos e anotações diferentes), mas existem certos elementos comuns entre eles, como um nome.

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

E essa seria a anotação comum:

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

No exemplo acima, eu esperaria que o Move herde o método3, mas recebo um aviso informando que estende não é válido com anotações. Eu estava tentando fazer uma anotação estender uma base comum, mas isso não funciona. Isso é possível ou é apenas uma questão de design?


3
A herança de anotação parece um item obrigatório para a criação de uma DSL com base em anotações. Pena que a herança de anotação não seja suportada.
Ceki

2
Eu concordo, parece uma coisa natural a se fazer. Especialmente depois de entender a herança em Java, você espera que ela se aplique a tudo.
Javydreamercsw

Respostas:


76

Infelizmente não. Aparentemente, tem algo a ver com programas que lêem as anotações em uma classe sem carregá-las até o fim. Consulte Por que não é possível estender anotações em Java?

No entanto, os tipos herdam as anotações de sua superclasse se essas anotações forem @Inherited.

Além disso, a menos que você precise desses métodos para interagir, basta empilhar as anotações em sua classe:

@Move
@Page
public class myAwesomeClass {}

Existe algum motivo que não funcionaria para você?


1
Foi o que pensei, mas estava tentando simplificar as coisas. Talvez a aplicação do Common um para uma classe abstrata faria o truque ...
javydreamercsw

1
Sim, isso parecia muito inteligente também. Boa sorte!
andronikus

67

Você pode anotar sua anotação com uma anotação base em vez de herança. Isso é usado no framework Spring .

Para dar um exemplo

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

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

@Car
class Foo {
}

Você pode então verificar se uma classe está anotada Vehicleusando o AnnotationUtils do Spring :

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

Este método é implementado como:

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

AnnotationUtilstambém contém métodos adicionais para procurar anotações em métodos e outros elementos anotados. A classe Spring também é poderosa o suficiente para pesquisar métodos, proxies e outros casos extremos, principalmente aqueles encontrados no Spring.


13
Inclua uma explicação de como processar essas anotações.
Aleksandr Dubinsky

1
Você pode usar AnnotationUtils.findAnnotation da Primavera (..), consulte: docs.spring.io/spring/docs/current/javadoc-api/org/...
rgrebski

2
Quando a anotação A é anotada com outra anotação B e anotamos a classe C com A, a classe C é tratada como se fosse anotada com A e B. Esse é o comportamento específico da estrutura Spring - AnnotationUtils.findAnnotation faz a mágica aqui e é usado para percorrer para cima para encontrar anotações de uma anotação. Portanto, não entenda mal que esse é o comportamento padrão do Java em relação à manipulação de anotações.
Qartal

Isso só é possível se as anotações que você deseja compor tiverem um destino de TYPEou ANNOTATION_TYPE.
OrangeDog 16/06

7

Além da resposta da Grygoriys de anotações.

Você pode verificar, por exemplo, métodos para conter uma @Qualifieranotação (ou uma anotação anotada com @Qualifier) por este loop:

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

O que você está basicamente fazendo é obter todas as anotações presentes no método e, dessas anotações, você obtém seus tipos e verifica esses tipos se eles estão anotados com @Qualifier. Sua anotação precisa estar também com Target.Annotation_type para que isso funcione.


Como isso é diferente da resposta de Grygoriy?
Aleksandr Dubinsky

@AleksandrDubinsky: É apenas uma implementação muito mais simples sem usar o Spring. Ele não encontra recursivamente anotações anotadas, mas acho que isso geralmente não é necessário. Eu gosto da simplicidade desta solução.
Stefan Steinegger

1

Confira https://github.com/blindpirate/annotation-magic , que é uma biblioteca que desenvolvi quando tive a mesma pergunta.

@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()));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.