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?
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.
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);
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.
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ù).
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.