Valores de anotaciones de Java proporcionados de forma dinámica

Resuelto thelost asked hace 12 años • 3 respuestas

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

Se piensa GENERIC_GENERATED_NAMEque static finalse queja de que

El valor del atributo de anotación MyInterface.aStringdebe ser una expresión constante.

Entonces, ¿cómo lograr esto?

thelost avatar May 17 '12 19:05 thelost
Aceptado

No hay forma de generar dinámicamente una cadena utilizada en una anotación. El compilador evalúa los metadatos de las RetentionPolicy.RUNTIMEanotaciones en el momento de la compilación, pero GENERIC_GENERATED_NAMEno se conoce hasta el tiempo de ejecución. Y no puede usar valores generados para anotaciones que se deben RetentionPolicy.SOURCEa que se descartan después del tiempo de compilación, por lo que esos valores generados nunca se conocerán.

Tim Pote avatar May 17 '2012 13:05 Tim Pote

La solución es utilizar un método anotado. Llame a ese método (con reflexión) para obtener el valor dinámico.

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 sencillo.

// 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 avatar Aug 12 '2014 00:08 juhoautio

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

  1. Asigne una expresión a la propiedad en la anotación y procese esa expresión cada vez que recupere la anotación. En su caso su anotación puede ser

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

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

  1. Esta es una manera hacky de hacer lo que queremos. Java almacena una variable privada que mantiene un mapa de anotaciones en la clase/campo/método. Puedes usar la reflexión y hacerte con ese mapa. Entonces, mientras procesamos la anotación por primera vez, resolvemos la expresión y encontramos 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 anotaciones depende de la versión de Java y no es confiable ya que no está expuesta para su uso (es privada).

Puede encontrar una implementación de referencia aquí.

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

PD: No he probado el segundo método.

yaswanth avatar Jan 21 '2017 15:01 yaswanth