Modify a class definition's annotation string parameter at runtime

后端 未结 7 1775
[愿得一人]
[愿得一人] 2020-11-22 04:55

Imagine there is a class:

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

Which is already compiled (I cannot c

相关标签:
7条回答
  • 2020-11-22 05:01

    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();
    }
    
    0 讨论(0)
  • 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 {
        //...
    }
    
    0 讨论(0)
  • 2020-11-22 05:14

    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.

    0 讨论(0)
  • 2020-11-22 05:16

    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();
    }
    
    0 讨论(0)
  • 2020-11-22 05:21

    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?

    0 讨论(0)
  • 2020-11-22 05:22

    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();
    }
    
    0 讨论(0)
提交回复
热议问题