web-dev-qa-db-fra.com

Les valeurs Java Annotations fournies de manière dynamique

Je veux fournir des annotations avec certaines valeurs générées par certaines méthodes.

J'ai essayé jusqu'à présent:

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

Pensé GENERIC_GENERATED_NAME est static final, il se plaint que 

La valeur de l'attribut d'annotation MyInterface.aString doit être une expression constante.

Alors, comment y parvenir?

18
thelost

Il n'y a aucun moyen de générer dynamiquement une chaîne utilisée dans une annotation. Le compilateur évalue les métadonnées d'annotation pour les annotations RetentionPolicy.RUNTIME au moment de la compilation, mais GENERIC_GENERATED_NAME n'est pas connu avant l'exécution. De plus, vous ne pouvez pas utiliser les valeurs générées pour les annotations qui sont RetentionPolicy.SOURCE car elles sont ignorées après la compilation. Par conséquent, ces valeurs générées seraient jamais connues.

17
Tim Pote

La solution consiste à utiliser une méthode annotée à la place. Appelez cette méthode (avec réflexion) pour obtenir la valeur dynamique.

Du point de vue de l'utilisateur, nous aurions:

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

L'annotation elle-même serait définie comme

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

La mise en œuvre de la recherche pour ces deux annotations est plutôt simple.

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

Il n’existe aucun moyen de modifier les propriétés d’une annotation de façon dynamique, comme d’autres le disent. Néanmoins, si vous voulez y parvenir, il y a deux façons de le faire.

  1. Attribuez une expression à la propriété dans l'annotation et traitez cette expression chaque fois que vous récupérez l'annotation. Dans votre cas, votre annotation peut être

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

Lorsque vous lisez cela, vous pouvez traiter la chaîne, invoquer la méthode et récupérer la valeur. Spring le fait par SPEL (langage d’expression Spring). Cela nécessite beaucoup de ressources et les cycles de processeur sont gaspillés chaque fois que nous voulons traiter l'expression. Si vous utilisez spring, vous pouvez accrocher un beanPostProcessor, traiter l'expression une fois et stocker le résultat quelque part. (Soit un objet de propriétés globales ou dans une carte pouvant être récupérée n'importe où).

  1. C’est une façon détournée de faire ce que nous voulons. Java stocke une variable privée qui maintient une carte d'annotations sur la classe/le champ/la méthode. Vous pouvez utiliser la réflexion et mettre la main sur cette carte. Ainsi, lors du traitement de l'annotation pour la première fois, nous résolvons l'expression et trouvons la valeur réelle. Ensuite, nous créons un objet d'annotation du type requis. Nous pouvons placer l'annotation nouvellement créée avec la valeur réelle (qui est constante) sur la propriété de l'annotation et remplacer celle-ci dans la carte extraite.

La manière dont jdk stocke la carte d'annotation dépend de la version Java et n'est pas fiable car elle n'est pas exposée à l'utilisation (elle est privée).

Vous pouvez trouver une implémentation de référence ici.

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

P.S: Je n'ai pas essayé et testé la deuxième méthode.

0
yaswanth