web-dev-qa-db-de.com

Ändern Sie den Anmerkungs-String-Parameter einer Klassendefinition zur Laufzeit

Stellen Sie sich vor, es gibt eine Klasse:

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

Welches ist bereits kompiliert (ich kann die Quelle nicht steuern) und ist Teil des Klassenpfads, wenn jvm gestartet wird. Ich möchte in der Lage sein, "irgendein Wert" zur Laufzeit in einen anderen Wert zu ändern, so dass jede Reflexion danach meinen neuen Wert anstelle des Standardwerts "irgendein Wert" hätte.

Ist das möglich? Wenn das so ist, wie?

49
Richard Pianka

Dieser Code entspricht mehr oder weniger dem, was Sie verlangen - es ist ein einfacher Proof of Concept:

  • eine korrekte Implementierung muss sich auch mit der declaredAnnotations beschäftigen.
  • wenn sich die Implementierung von Annotationen in Class.Java ändert, wird der Code beschädigt (d. h. er kann jederzeit in der Zukunft brechen).
  • Ich habe keine Ahnung, ob es Nebenwirkungen gibt ...

Ausgabe:

oldAnnotation = ein Wert
modifiedAnnotation = ein anderer Wert

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

Warnung: Nicht unter OSX getestet - siehe Kommentar von @Marcel

Unter OSX getestet. Funktioniert gut.

Da ich zur Laufzeit auch die Anmerkungswerte ändern musste, habe ich diese Frage nochmals aufgegriffen.

Hier ist eine modifizierte Version des @ assylias-Ansatzes (vielen Dank für die 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;
}

Anwendungsbeispiel:

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

Der Vorteil dieses Ansatzes besteht darin, dass keine neue Anmerkungsinstanz erstellt werden muss. Daher muss man die konkrete Anmerkungsklasse nicht im Voraus kennen. Auch sollten die Nebenwirkungen minimal sein, da die ursprüngliche Anmerkungsinstanz nicht berührt wird. 

Mit Java 8 getestet.

30
Balder

Dieses funktioniert auf meinem Rechner mit Java 8. Es ändert den Wert von ignoreUnknown in der Anmerkung @JsonIgnoreProperties(ignoreUnknown = true) von true in 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

Versuchen Sie diese Lösung für 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 kann diesen Job sehr leicht erledigen. Dies kann für den Frühjahrsentwickler nützlich sein. __ Befolgen Sie diese Schritte: -

Erste Lösung: - 1) Erstellen Sie ein Bean, das einen Wert für someProperty zurückgibt. Hier habe ich den somePropertyValue mit @Value-Annotation aus der DB- oder Property-Datei injiziert: -

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

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

2) Danach ist es möglich, somePropertyValue wie folgt in die @Something-Anmerkung zu injizieren:

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

Zweite Lösung: -

1) Getter Setter in Bean erstellen: - 

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

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

2) Danach ist es möglich, somePropertyValue wie folgt in die @Something-Anmerkung zu injizieren:

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

ich kann in jdk1.8 auf diese Weise auf Anmerkungen zugreifen und diese ändern, weiß aber nicht, warum dies keine Auswirkung hat.

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