40

What are valid use cases for implementing annotations?

When designing primarily annotation based configuration systems I occasionally need to create classes which implement annotations for code generation or programmatic configuration.

The alternative involves mirroring the data contained in annotations into DTOs, which seems like an overhead.

Here is an example:

public enum IDType {
    LOCAL,
    URI,
    RESOURCE;
}

@Documented
@Target( { METHOD, FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Id {
    /**
     * @return
     */
    IDType value() default IDType.LOCAL;
}

with the implementation

public class IdImpl implements Id{

    private final IDType idType;

    public IdImpl(IDType idType){
        this.idType = idType;
    }

    @Override
    public IDType value() {
        return idType;
    }

    @Override
    public Class<? extends Annotation> annotationType() {
        return Id.class;
    }

}

I get compiler warnings for this, but it seems to be a valid tool for many use cases.

The warning for the example above is

The annotation type Id should not be used as a superinterface for IdImpl

Edited :

I just found this example from Guice :

bind(CreditCardProcessor.class)
    .annotatedWith(Names.named("Checkout"))
    .to(CheckoutCreditCardProcessor.class);

See this Javadoc from Names.

Has anyone some information why this restriction exists or has some other use cases in mind?

Timo Westkämper
  • 21,824
  • 5
  • 78
  • 111
  • 2
    @djina: Yeah, one penny for every time you have to say that and you'd be rich. It never ceases to amaze me. – musiKk Jul 27 '10 at 08:56
  • Maybe it's just me but it seems to be closely related to this question: http://stackoverflow.com/questions/1624084/why-is-not-possible-to-extend-annotations-in-java – j flemm Jul 27 '10 at 18:22
  • @j-flemm, Annotation subtyping is a different thing. Annotation subtyping is impossible, implementing annotations is possible, but gives compiler warnings – Timo Westkämper Jul 27 '10 at 19:08

3 Answers3

27

I've never used it in practice but what you get is, that you can use classes as replacement for your annotations.

Let's create an artificial example. Say we have an documentation generator. It reads a @Docu annotation from given classes and prints the description attribute. Like this:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

public class DokuGenerator {

    public static void main(String[] args) throws Exception {
        new DokuGenerator(StaticClass.class, StaticClass2.class);
    }

    public DokuGenerator(Class<?>... classesToDokument) throws Exception {
        List<Docu> documentAnnotations = getDocumentAnnotations(classesToDokument);
        printDocumentation(documentAnnotations);
    }

    private List<Docu> getDocumentAnnotations(Class<?>... classesToDokument)
            throws Exception {
        List<Docu> result = new ArrayList<Docu>();
        for (Class<?> c : classesToDokument)
            if (c.isAnnotationPresent(Docu.class))
                result.add(c.getAnnotation(Docu.class));
        return result;
    }

    private void printDocumentation(List<Docu> toDocument) {
        for (Docu m : toDocument)
            System.out.println(m.description());
    }

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Docu {
    String description();
}

@Docu(description = "This is a static class!")
class StaticClass {
}

@Docu(description = "This is another static class!")
class StaticClass2 {
}

Prints:

This is a static class!  
This is another static class!

What we now want to accomplish is, that a class can not only be staticly annotated, but can add runtime information to the documentation. We are quite happy to use the @Docu annotation most of the time, but there are special cases we want special documenation. We might want to add performance documenation for some methodes. We can do this by letting a class implement the annotation. The generator checks first for the annotation and, if not present, it checks if the class implements the annotation. If it does, it adds the class to the list of annotations.

Like this (only two additional lines of code in the generator):

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DokuGenerator {

    public static void main(String[] args) throws Exception {
        new DokuGenerator(StaticClass.class, StaticClass2.class,
                DynamicClass.class);
    }

    public DokuGenerator(Class<?>... classesToDokument) throws Exception {
        List<Docu> documentAnnotations = getDocumentAnnotations(classesToDokument);
        printDocumentation(documentAnnotations);
    }

    private List<Docu> getDocumentAnnotations(Class<?>... classesToDokument)
            throws Exception {
        List<Docu> result = new ArrayList<Docu>();
        for (Class<?> c : classesToDokument)
            if (c.isAnnotationPresent(Docu.class))
                result.add(c.getAnnotation(Docu.class));
            else if (Arrays.asList(c.getInterfaces()).contains(Docu.class))
                result.add((Docu) c.newInstance());
        return result;
    }

    private void printDocumentation(List<Docu> toDocument) {
        for (Docu m : toDocument)
            System.out.println(m.description());
    }

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Docu {
    String description();
}

@Docu(description = "This is a static class!")
class StaticClass {
}

@Docu(description = "This is another static class!")
class StaticClass2 {
}

class DynamicClass implements Docu {

    public DynamicClass() {
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
            // ignore exception to make debugging a little harder
        }
    }

    @Override
    public String description() {
        long millis = System.currentTimeMillis();
        new DynamicClass();
        millis = System.currentTimeMillis() - millis;
        return "This is a dynamic class. I run on "
                + System.getProperty("os.name")
                + ". The construction of an instance of this class run for "
                + millis + " milliseconds.";
    }

    @Override
    public Class<? extends Annotation> annotationType() {
        return Docu.class;
    }

}

Output is:

This is a static class!  
This is another static class!  
This is a dynamic class. I run on Windows XP. The construction of an instance of this class run for 47 milliseconds.

You havn't to change the code generator that much because you can use the class as replacement of the annotation.

Other example whould be a framework that uses annotations or XML as configuration. You might have one processor that works on annotations. If you use XML as configuration you can generate instances of classes that implement the annotations and your processor works on them without a single change! (of course there are other ways to accomplish the same effect, but this is ONE way to do it)

gobernador
  • 5,659
  • 3
  • 32
  • 51
Arne Deutsch
  • 14,629
  • 5
  • 53
  • 72
  • thanks for the detailed answer. Classes as replacements for reflection derived annotation instances are indeed a valid case. Annotations are quite handy as configuration data and for dynamic cases instantiating them is necessary. – Timo Westkämper Jul 28 '10 at 15:45
  • 2
    "// ignore exception to make debugging a little harder" +1 – Birb Jul 05 '16 at 08:43
0

I use it when I created an annotation and want to make its use optional, by providing a default when the annotation is omitted. This could occur when your library introduces a new annotation and you want your library to remain backwards compatible.

In this example, BeanB is written in source code against an older version of your library, so you want to use a default when you identify such a class.

@Id
class BeanA {}

// No annotation
class BeanB {}

The default implementation;

private static final Id DEFAULT_ID = new Id() {

    @Override
    public IDType value() {
        return IDType.LOCAL;
    }

    @Override
    public Class<? extends Annotation> annotationType() {
        return Id.class;
    }
};

Processing;

Id beanId = (bean.getClass().isAnnotationPresent(Id.class))
    ? bean.getClass().getAnnotation(Id.class)
    : DEFAULT_ID;
Foumpie
  • 1,465
  • 1
  • 14
  • 10
  • This is certainly valid, but you can also provide default values as part of the annotation interface itself so that you don't really need to do this.. – ManoDestra Mar 16 '18 at 15:49
  • 2
    @ManoDestra But the second class does not have the annotation, so there are no default values. This could occur for example when you want backwards compatibility support for source code that does not use the newly introduced annotation. – Foumpie May 02 '18 at 15:01
-2

There are no valid user cases for that - compiler just tollerates it since it would be quite messy to forbid it and people who are writing compilers may need the facility on a very rare occasion. If you need to classify annotations check out this article to see how to do it: Why is not possible to extend annotations in Java?

Inagine a poor soul coming after you to maintain and debug that code or another one who needs to write a codegen tool and assumes that annotatuion types are straight or another who just used such annotation not even dreaming what can happend and what to do about it. By the time he discovers that hack and finds a way to eliminate it he's going to die from hernia - or equivalent ailment :-) Annotations are expected to be purely declarative statements, interpreted solely by a codegen tool which runs separately from the annotated code and treats it as data.

Take a fresh look at that code and try to honestly say what's a rational rason for something like:

 public Class<? extends Annotation> annotationType() { 
     return Id.class; 
 } 

and that's stil a small thing compared to that people can put in code.

Annotations are not the place to practice hacking - that's what compiler is trying to convey. Do you know exactly when and how the code in "implementation" of annotation may run? Including CTOR? What is available and what not at that point? What is safe to call? Compiler doesn't - it would take pretty heavy static analysis for a compiler to check actual safety of such hack. So instead it just issues a warning so that when something goes wrong people can't blame compile, VM and everything else.

Community
  • 1
  • 1
ZXX
  • 4,684
  • 27
  • 35
  • I am not interested in classifying annotations and my use cases are not related to compilers. And I don't consider my use cases "hacking". – Timo Westkämper Jul 28 '10 at 13:43
  • @ZZX, your example was missing the javadoc which hopefully holds the answer to _why?_. – Thorbjørn Ravn Andersen Jan 25 '11 at 21:21
  • Your overreaction really isn't warranted. Here's one very valid use case for implementing annotations: You want to run annotation processors that supply metadata using an unweildly api, like javax.lang.model, which bails on trying to load class values. Better to write an annotation processor that scans annotations and generates an annotation builder which actually can operate on class values. (javax.lang.model force you to catch an undeclared runtime exception and extract class type from a type mirror; generated builder takes care of that ugly mess in other annotation processors). – Ajax Apr 22 '13 at 08:42