Modify a class definition's annotation string parameter at runtime

JavaReflection

Java Problem Overview


Imagine there is a class:

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

Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".

Is this possible? If so, how?

Java Solutions


Solution 1 - Java

Warning: Not tested on OSX - see comment from @Marcel

Tested on OSX. Works fine.

Since I also had the need to change annotation values at runtime, I revisited this question.

Here is a modified version of @assylias approach (many thanks for the 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;
}

Usage example:

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

The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.

Tested with Java 8.

Solution 2 - Java

This code does more or less what you ask for - it is a simple proof of concept:

  • a proper implementation needs to also deal with the declaredAnnotations
  • if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
  • I have no idea if there are side effects...

Output: >oldAnnotation = some value
modifiedAnnotation = another value

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

Solution 3 - Java

This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation @JsonIgnoreProperties(ignoreUnknown = true) from true to 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);

Solution 4 - Java

SPRING can do this job very easily , might be useful for spring developer . follow these steps :-

First Solution :- 1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with @Value annotation from DB or property file :-

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

2)After this , it is possible to inject the somePropertyValue into the @Something annotation like this :-

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

Second solution :-

  1. create getter setter in bean :-

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

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

2)After this , it is possible to inject the somePropertyValue into the @Something annotation like this :-

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

Solution 5 - Java

Try this solution for 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();
}

Solution 6 - Java

i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,

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

Solution 7 - Java

Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRichard PiankaView Question on Stackoverflow
Solution 1 - JavaBalderView Answer on Stackoverflow
Solution 2 - JavaassyliasView Answer on Stackoverflow
Solution 3 - JavaPatzeView Answer on Stackoverflow
Solution 4 - JavaVijayView Answer on Stackoverflow
Solution 5 - Javarahuliyer95View Answer on Stackoverflow
Solution 6 - JavaMuthahir NawazView Answer on Stackoverflow
Solution 7 - JavaTom McIntyreView Answer on Stackoverflow