Los valores de las anotaciones Java se proporcionan de manera dinámica

Deseo proporcionar anotaciones con algunos valores generados por algunos métodos.

Intenté esto hasta ahora:

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

Pensamiento GENERIC_GENERATED_NAME es static final , se queja de que

El valor para el atributo de anotación MyInterface.aString debe ser una expresión constante

Entonces, ¿cómo lograr esto?

No hay forma de generar dinámicamente una cadena utilizada en una anotación. El comstackdor evalúa los metadatos de anotación para las anotaciones RetentionPolicy.RUNTIME en tiempo de comstackción, pero GENERIC_GENERATED_NAME no se conoce hasta el tiempo de ejecución. Y no puede usar valores generados para anotaciones que son RetentionPolicy.SOURCE porque se descartan después del tiempo de comstackción, por lo que esos valores generados nunca se conocerán.

La solución es usar un método anotado en su lugar. Llame a ese método (con reflexión) para obtener el valor dynamic.

Desde la perspectiva del usuario, tendríamos:

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

La anotación en sí se definiría como

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

Implementar la búsqueda de ambas anotaciones es bastante directo.

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

No hay forma de modificar las propiedades de una anotación dinámicamente como otros dijeron. Aún así, si desea lograr eso, hay dos formas de hacerlo.

  1. Asigne una expresión a la propiedad en la anotación y procese esa expresión siempre que recupere la anotación. En tu caso, tu anotación puede ser

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

Cuando lo lea, puede procesar la cadena y hacer la invocación del método y recuperar el valor. Spring lo hace por SPEL (lenguaje de expresión de spring). Esto consume muchos recursos y los ciclos de CPU se desperdician cada vez que queremos procesar la expresión. Si está utilizando la spring, puede conectar un beanPostProcessor y procesar la expresión una vez y almacenar el resultado en alguna parte. (O un objeto de propiedades globales o en un mapa que se puede recuperar en cualquier lugar).

  1. Esta es una forma hacky de hacer lo que queremos. Java almacena una variable privada que mantiene un mapa de anotaciones en la clase / campo / método. Puede usar la reflexión y obtener ese mapa. Por lo tanto, al procesar la anotación por primera vez, resolvemos la expresión y buscamos el valor real. Luego creamos un objeto de anotación del tipo requerido. Podemos colocar la anotación recién creada con el valor real (que es constante) en la propiedad de la anotación y anular la anotación real en el mapa recuperado.

La forma en que jdk almacena el mapa de anotación depende de la versión java y no es confiable, ya que no está expuesto para su uso (es privado).

Puede encontrar una implementación de referencia aquí.

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

PD: No he probado y probado el segundo método.