151

I'm exploring annotations and came to a point where some annotations seems to have a hierarchy among them.

I'm using annotations to generate code in the background for Cards. There are different Card types (thus different code and annotations) but there are certain elements that are common among them like a name.

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

And this would be the common Annotation:

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

In the example above I would expect Move to inherit method3 but I get a warning saying that extends is not valid with annotations. I was trying to have an Annotation extends a common base one but that doesn't work. Is that even possible or is just a design issue?

javydreamercsw
  • 5,363
  • 13
  • 61
  • 106
  • 3
    Annotation inheritance seems like a must-have for creating a DSL based on annotations. Such a pity that annotation inheritance is not supported. – Ceki Apr 15 '13 at 19:09
  • 2
    I agree, seems like a natural thing to do. Especially after to understand inheritance on Java, you kind of expect it to apply to everything. – javydreamercsw Apr 15 '13 at 19:28

5 Answers5

93

You can annotate your annotation with a base annotation instead of inheritance. This is used in Spring framework.

To give an example

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

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

@Car
class Foo {
}

You can then check if a class is annotated with Vehicle using Spring's AnnotationUtils:

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

This method is implemented as:

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

AnnotationUtils also contains additional methods for searching for annotations on methods and other annotated elements. The Spring class is also powerful enough to search through bridged methods, proxies, and other corner-cases, particularly those encountered in Spring.

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
Grygoriy Gonchar
  • 3,898
  • 1
  • 24
  • 16
  • 13
    Please include an explanation of how to process such annotations. – Aleksandr Dubinsky Dec 04 '13 at 10:16
  • 1
    You can use Spring's AnnotationUtils.findAnnotation(..), see: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html#findAnnotation-java.lang.Class-java.lang.Class- – rgrebski May 11 '15 at 14:13
  • 7
    When the annotation A is annotated with another annotation B, and we annotate class C with A, class C is treated as if it is annotated with both A and B. This is the specific behavior of Spring framework -- AnnotationUtils.findAnnotation does the magic here and is used to traverse up to find annotations of an annotation. So do not misunderstand that this is the default behavior of Java concerning annotation handling. – qartal Apr 25 '18 at 16:37
  • 2
    This is only possible if the annotations you wish to compose have a target of `TYPE` or `ANNOTATION_TYPE`. – OrangeDog Jun 16 '20 at 10:44
91

Unfortunately, no. Apparently it has something to do with programs that read the annotations on a class without loading them all the way. See Why is it not possible to extend annotations in Java?

However, types do inherit the annotations of their superclass if those annotations are @Inherited.

Also, unless you need those methods to interact, you could just stack the annotations on your class:

@Move
@Page
public class myAwesomeClass {}

Is there some reason that wouldn't work for you?

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
andronikus
  • 4,125
  • 2
  • 29
  • 46
  • 1
    That was what I thought but I was trying to simplify things. Maybe applying the Common one to an abstract class would do the trick... – javydreamercsw Oct 14 '11 at 02:16
13

In addition to Grygoriys answer of annotating annotations.

You can check e.g. methods for containing a @Qualifier annotation (or an annotation annotated with @Qualifier) by this loop:

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

What you're basically doing, is to get all annotations present on the method and of those annotations you get their types and check those types if they're annotated with @Qualifier. Your annotation needs to be Target.Annotation_type enabled as well to get this working.

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
philnate
  • 1,506
  • 2
  • 21
  • 39
4

Check out https://github.com/blindpirate/annotation-magic , which is a library I developed when I had the same 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
  • 41
  • 1
  • 1
0

You can @nnotate your @interface like this:

@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Page{
    String method3();
}
  • I don't think this is what has been asked. If I understand this correctly this makes it so that placing that annotation on a class implies that annotation for child classes of that class. What I think was asked is a way to make one annotation imply another annotation. – Skgland Jun 03 '23 at 07:52