web-dev-qa-db-fra.com

Modifier le paramètre de chaîne d'annotation d'une définition de classe au moment de l'exécution

Imaginez qu'il y ait un cours:

@Something(someProperty = "some value")
public class Foobar {
    //...
}

Qui est déjà compilé (je ne peux pas contrôler la source) et fait partie du chemin de classe lorsque jvm démarre. J'aimerais pouvoir changer "une valeur" en autre chose au moment de l'exécution, de sorte que toute réflexion ultérieure ait ma valeur nouvelle valeur au lieu de la valeur par défaut "une valeur".

Est-ce possible? Si c'est le cas, comment?

49
Richard Pianka

Ce code fait plus ou moins ce que vous demandez - c’est une simple preuve de concept:

  • une mise en œuvre appropriée doit également traiter de la declaredAnnotations
  • si la mise en œuvre des annotations dans Class.Java change, le code sera cassé (c’est-à-dire qu’il peut le faire à tout moment dans le futur).
  • Je ne sais pas s'il y a des effets secondaires ...

Sortie:

oldAnnotation = une valeur
modifiedAnnotation = une autre valeur

public static void main(String[] args) throws Exception {
    final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
    Annotation newAnnotation = new Something() {

        @Override
        public String someProperty() {
            return "another value";
        }

        @Override
        public Class<? extends Annotation> annotationType() {
            return oldAnnotation.annotationType();
        }
    };
    Field field = Class.class.getDeclaredField("annotations");
    field.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
    annotations.put(Something.class, newAnnotation);

    Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}

@Something(someProperty = "some value")
public static class Foobar {
}

@Retention(RetentionPolicy.RUNTIME)
@interface Something {

    String someProperty();
}
39
assylias

Attention: Non testé sur OSX - voir le commentaire de @Marcel

Testé sur OSX. Fonctionne bien.

Comme j'avais également besoin de modifier les valeurs d'annotation au moment de l'exécution, j'ai réexaminé cette question.

Voici une version modifiée de l'approche @assylias (merci beaucoup pour l'inspiration).

/**
 * Changes the annotation value for the given key of the given annotation to newValue and returns
 * the previous value.
 */
@SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
    Object handler = Proxy.getInvocationHandler(annotation);
    Field f;
    try {
        f = handler.getClass().getDeclaredField("memberValues");
    } catch (NoSuchFieldException | SecurityException e) {
        throw new IllegalStateException(e);
    }
    f.setAccessible(true);
    Map<String, Object> memberValues;
    try {
        memberValues = (Map<String, Object>) f.get(handler);
    } catch (IllegalArgumentException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
    Object oldValue = memberValues.get(key);
    if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
        throw new IllegalArgumentException();
    }
    memberValues.put(key,newValue);
    return oldValue;
}

Exemple d'utilisation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotation {
  String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAnnotation {
  String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
  String value() default "";
}
@ClassAnnotation("class test")
public static class TestClass{
    @FieldAnnotation("field test")
    public Object field;
    @MethodAnnotation("method test")
    public void method(){

    }
}

public static void main(String[] args) throws Exception {
    final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
    System.out.println("old ClassAnnotation = " + classAnnotation.value());
    changeAnnotationValue(classAnnotation, "value", "another class annotation value");
    System.out.println("modified ClassAnnotation = " + classAnnotation.value());

    Field field = TestClass.class.getField("field");
    final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
    System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
    changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
    System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());

    Method method = TestClass.class.getMethod("method");
    final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
    System.out.println("old MethodAnnotation = " + methodAnnotation.value());
    changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
    System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}

L'avantage de cette approche est qu'il n'est pas nécessaire de créer une nouvelle instance d'annotation. Par conséquent, il n'est pas nécessaire de connaître la classe d'annotation concrète à l'avance. De plus, les effets secondaires doivent être minimes, car l'instance d'annotation d'origine reste intacte. 

Testé avec Java 8.

30
Balder

Celui-ci fonctionne sur ma machine avec Java 8. Il change la valeur de ignoreUnknown dans l'annotation @JsonIgnoreProperties(ignoreUnknown = true) de true en false .

final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());    

final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
    @Override public Class<? extends Annotation> annotationType() {
        return matchedAnnotation.get(0).annotationType();
    }    @Override public String[] value() {
        return new String[0];
    }    @Override public boolean ignoreUnknown() {
        return false;
    }    @Override public boolean allowGetters() {
        return false;
    }    @Override public boolean allowSetters() {
        return false;
    }
};    

final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
3
Patze

Essayez cette solution pour Java 8

public static void main(String[] args) throws Exception {
    final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
    Annotation newAnnotation = new Something() {

        @Override
        public String someProperty() {
            return "another value";
        }

        @Override
        public Class<? extends Annotation> annotationType() {
            return oldAnnotation.annotationType();
        }
    };
    Method method = Class.class.getDeclaredMethod("annotationData", null);
    method.setAccessible(true);
    Object annotationData = method.invoke(getClass(), null);
    Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
    declaredAnnotations.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
    annotations.put(Something.class, newAnnotation);

    Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}

@Something(someProperty = "some value")
public static class Foobar {
}

@Retention(RetentionPolicy.RUNTIME)
@interface Something {
    String someProperty();
}
1
rahuliyer95

SPRING peut faire ce travail très facilement, pourrait être utile pour le développeur de printemps . Suivez ces étapes: -

Première solution: - 1) créer un bean renvoyant une valeur pour someProperty. Ici, j'ai injecté l'annotation @Value de somePropertyValue à partir de la base de données ou du fichier de propriétés: -

    @Value("${config.somePropertyValue}")
    private String somePropertyValue;

    @Bean
    public String somePropertyValue(){
        return somePropertyValue;
    }

2) Après cela, il est possible d'injecter la somePropertyValue dans l'annotation @Something comme ceci: -

@Something(someProperty = "#{@somePropertyValue}")
public class Foobar {
    //...
}

Deuxième solution: -

1) créer getter setter dans le haricot: - 

 @Component
    public class config{
         @Value("${config.somePropertyValue}")
         private String somePropertyValue;

         public String getSomePropertyValue() {
           return somePropertyValue;
         }
        public void setSomePropertyValue(String somePropertyValue) {
           this.somePropertyValue = somePropertyValue;
        }
    }

2) Après cela, il est possible d'injecter la somePropertyValue dans l'annotation @Something comme ceci: -

@Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
    //...
}
0
Vijay Gupta

je suis en mesure d'accéder et de modifier les annotations de cette manière dans jdk1.8, mais je ne sais pas pourquoi, cela n'a aucun effet,

try {
    Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
    annotationDataField.setAccessible(true);
    Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
    annotationsField.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations =  (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
    annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
} catch (SecurityException e) {    
    e.printStackTrace();
}
0
Muthahir Nawaz