¿Existe algo parecido a la herencia de anotaciones en Java?

119

Estoy explorando anotaciones y llegué a un punto en el que algunas anotaciones parecen tener una jerarquía entre ellas.

Estoy usando anotaciones para generar código en segundo plano para Cards. Hay diferentes tipos de tarjetas (por lo tanto, diferentes códigos y anotaciones) pero hay ciertos elementos que son comunes entre ellos, como un nombre.

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

Y esta sería la anotación común:

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

En el ejemplo anterior, esperaría que Move herede el método3, pero recibo una advertencia que dice que la extensión no es válida con anotaciones. Estaba tratando de que una Anotación extendiera una base común, pero eso no funciona. ¿Es eso posible o es solo una cuestión de diseño?

javydreamercsw
fuente
3
La herencia de anotaciones parece imprescindible para crear un DSL basado en anotaciones. Es una pena que no se admita la herencia de anotaciones.
Ceki
2
Estoy de acuerdo, parece algo natural. Especialmente después de comprender la herencia en Java, esperas que se aplique a todo.
javydreamercsw

Respuestas:

76

Lamentablemente no. Aparentemente tiene algo que ver con programas que leen las anotaciones en una clase sin cargarlas por completo. Consulte ¿Por qué no es posible ampliar las anotaciones en Java?

Sin embargo, los tipos heredan las anotaciones de su superclase si esas anotaciones lo son @Inherited.

Además, a menos que necesite esos métodos para interactuar, puede apilar las anotaciones en su clase:

@Move
@Page
public class myAwesomeClass {}

¿Hay alguna razón por la que no funcione para usted?

andronikus
fuente
1
Eso fue lo que pensé, pero estaba tratando de simplificar las cosas. Tal vez aplicar el Común a una clase abstracta haría el truco ...
javydreamercsw
1
Sí, eso también se veía bastante inteligente. ¡Buena suerte!
andronikus
67

Puede anotar su anotación con una anotación base en lugar de una herencia. Esto se usa en el marco de Spring .

Para dar un ejemplo

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

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

@Car
class Foo {
}

Luego puede verificar si una clase está anotada Vehicleusando Spring's AnnotationUtils :

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

Este método se implementa 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);
}

AnnotationUtilstambién contiene métodos adicionales para buscar anotaciones en métodos y otros elementos anotados. La clase Spring también es lo suficientemente poderosa como para buscar a través de métodos puenteados, proxies y otros casos de esquina, particularmente los encontrados en Spring.

Grygoriy Gonchar
fuente
13
Incluya una explicación de cómo procesar dichas anotaciones.
Aleksandr Dubinsky
1
Puede usar Spring's AnnotationUtils.findAnnotation (..), consulte: docs.spring.io/spring/docs/current/javadoc-api/org/…
rgrebski
2
Cuando la anotación A está anotada con otra anotación B, y anotamos la clase C con A, la clase C se trata como si estuviera anotada tanto con A como con B. Este es el comportamiento específico del marco de Spring - AnnotationUtils.findAnnotation hace la magia aquí y se usa para recorrer hacia arriba para encontrar anotaciones de una anotación. Así que no malinterprete que este es el comportamiento predeterminado de Java con respecto al manejo de anotaciones.
qartal
Esto solo es posible si las anotaciones que desea componer tienen un destino de TYPEo ANNOTATION_TYPE.
OrangeDog
7

Además de la respuesta de Grygoriy de anotar anotaciones.

Puede comprobar, por ejemplo, los métodos para contener una @Qualifieranotación (o una anotación anotada con @Qualifier) por este bucle:

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

Lo que básicamente está haciendo es obtener todas las anotaciones presentes en el método y de esas anotaciones obtiene sus tipos y verifica esos tipos si están anotados con @Qualifier. Su anotación debe estar habilitada para Target.Annotation_type también para que esto funcione.

filnate
fuente
¿En qué se diferencia esto de la respuesta de Grygoriy?
Aleksandr Dubinsky
@AleksandrDubinsky: Es una implementación mucho más simple sin usar Spring. No encuentra de forma recursiva anotaciones anotadas, pero supongo que normalmente no es necesario. Me gusta la sencillez de esta solución.
Stefan Steinegger
1

Consulte https://github.com/blindpirate/annotation-magic , que es una biblioteca que desarrollé cuando tuve la misma pregunta.

@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()));
usuario11949000
fuente