Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify a class definition's annotation string parameter at runtime

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?

like image 639
Richard Pianka Avatar asked Jan 10 '13 23:01

Richard Pianka


People also ask

How do I set annotation value at runtime?

Alter Annotation Java class Class maintains a map for managing annotations – Annotation class as keys and Annotation object as value: Map<Class<? extends Annotation>, Annotation> map; We will update this map to alter annotation at runtime.

How do you pass value to custom annotations?

Annotations require constant values and a method parameter is dynamic. Show activity on this post. Annotation Usage: @CacheClear(pathToVersionId = "[0]") public int importByVersionId(Long versionTo){ ...... }

What is the use of @interface annotation?

The @interface element is used to declare an annotation. For example: @interface MyAnnotation{}


2 Answers

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.

like image 68
Balder Avatar answered Sep 20 '22 07:09

Balder


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(); } 
like image 30
assylias Avatar answered Sep 20 '22 07:09

assylias