33

I want to provide annotations with some values generated by some methods.

I tried this so far:

public @interface MyInterface {
    String aString();
}

@MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {

    static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);

    public static final String generateName(final Class<?> c) {
        return c.getClass().getName();
    }
}

Thought GENERIC_GENERATED_NAME is static final, it complains that

The value for annotation attribute MyInterface.aString must be a constant expression

So how to achieve this ?

skaffman
  • 398,947
  • 96
  • 818
  • 769
thelost
  • 6,638
  • 4
  • 28
  • 44

3 Answers3

35

There is no way to dynamically generate a string used in an annotation. The compiler evaluates annotation metadata for RetentionPolicy.RUNTIME annotations at compile time, but GENERIC_GENERATED_NAME isn't known until runtime. And you can't use generated values for annotations that are RetentionPolicy.SOURCE because they are discarded after compile time, so those generated values would never be known.

Tim Pote
  • 27,191
  • 6
  • 63
  • 65
  • Ok, so I understand that this cannot be addressed by using annotations, right ? Maybe I will then have to run some tool before compilation and complete the Java source. Any other suggestion ? – thelost May 17 '12 at 13:32
  • @thelost I've never looked into it all that much to be honest. Let me look around a bit in my free time today and I'll try to get back to you on it. – Tim Pote May 17 '12 at 14:24
  • Sorry for replying this thread after 6 years :D, but is there any way to provide dynamic values inside annotations, if somehow we want user to specify a file name for example, and use that file name in annotation (for example Pointcuts in spring AOP)? – Dhruv Singhal Jul 26 '18 at 12:57
  • @DhruvSinghal If you have the file at compile time, you could use this approach: https://stackoverflow.com/questions/6525059/can-i-have-macros-in-java-source-files. Otherwise, you cannot modify source files during the runtime, and therefore you cannot modify the annotation values either. – SalmonKiller Feb 01 '19 at 17:15
8

The solution is to use an annotated method instead. Call that method (with reflection) to get the dynamic value.

From the user's perspective we'd have:

@MyInterface
public class MyClass {
    @MyName
    public String generateName() {
        return MyClass.class.getName();
    }
}

The annotation itself would be defined as

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface @MyName {
}

Implementing the lookup for both of these annotations is rather straight-forward.

// as looked up by @MyInterface
Class<?> clazz;

Method[] methods = clazz.getDeclaredMethods();
if (methods.length != 1) {
    // error
}
Method method = methods[0];
if (!method.isAnnotationPresent(MyName.class)) {
    // error as well
}
// This works if the class has a public empty constructor
// (otherwise, get constructor & use setAccessible(true))
Object instance = clazz.newInstance();
// the dynamic value is here:
String name = (String) method.invoke(instance);
juhoautio
  • 1,541
  • 1
  • 22
  • 23
  • In your case, maybe even use a static method. To call it via reflection, see http://stackoverflow.com/a/2467562/1068385 – juhoautio Aug 12 '14 at 00:58
6

There is no way to modify the properties of an annotation dynamically like others said. Still if you want to achieve that, there are two ways to do this.

  1. Assign an expression to the property in the annotation and process that expression whenever you retrieve the annotation. In your case your annotation can be

    @MyInterface(aString = "objectA.doSomething(args1, args2)")

When you read that, you can process the string and make the method invocation and retrieve the value. Spring does that by SPEL (Spring expression language). This is resource intensive and the cpu cycles are wasted every time we want to process the expression. If you are using spring, you can hook in a beanPostProcessor and process the expression once and store the result somewhere. (Either a global properties object or in a map which can be retrieved anywhere).

  1. This is a hacky way of doing what we want. Java stores a private variable which maintains a map of annotations on the class/field/method. You can use reflection and get hold of that map. So while processing the annotation for the first time, we resolve the expression and find the actual value. Then we create an annotation object of the required type. We can put the newly created annotation with the actual value (which is constant) on the property of the annotation and override the actual annotation in the retrieved map.

The way jdk stores the annotation map is java version dependent and is not reliable since it is not exposed for use (it is private).

You can find a reference implementation here.

https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/

P.S: I haven't tried and tested the second method.

yaswanth
  • 2,349
  • 1
  • 23
  • 33