【问题标题】:Modifying the declared annotations of a field during runtime using Java Reflection API使用 Java 反射 API 在运行时修改字段的声明注释
【发布时间】:2017-03-08 19:41:07
【问题描述】:

this answer 开始,可以通过创建和安装新的内部AnnotationData 对象在运行时向Java 类添加注释。我很好奇Field 是否可行。似乎Field 处理注释的方式与Class 处理它们的方式大不相同。

我已经能够使用以下类成功地将注释添加到Field 类的declaredAnnotations 字段:

public class FieldRuntimeAnnotations {

  private static final Field DECLARED_ANNOTATIONS_FIELD;
  private static final Method DECLARED_ANNOTATIONS_METHOD;

  static {
    try {
      DECLARED_ANNOTATIONS_METHOD = Field.class.getDeclaredMethod("declaredAnnotations");
      DECLARED_ANNOTATIONS_METHOD.setAccessible(true);

      DECLARED_ANNOTATIONS_FIELD = Field.class.getDeclaredField("declaredAnnotations");
      DECLARED_ANNOTATIONS_FIELD.setAccessible(true);

    } catch (NoSuchMethodException | NoSuchFieldException | ClassNotFoundException e) {
            throw new IllegalStateException(e);
    }
  }

  // Public access method
  public static <T extends Annotation> void putAnnotationToField(Field f, Class<T> annotationClass, Map<String, Object> valuesMap) {
    T annotationValues = TypeRuntimeAnnotations.annotationForMap(annotationClass, valuesMap);

    try {

        Object annotationData = DECLARED_ANNOTATIONS_METHOD.invoke(f);

        // Get declared annotations
        Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
                (Map<Class<? extends Annotation>, Annotation>) DECLARED_ANNOTATIONS_FIELD.get(f);

        // Essentially copy our original annotations to a new LinkedHashMap
        Map<Class<? extends Annotation>, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(declaredAnnotations);

        newDeclaredAnnotations.put(annotationClass, annotationValues);

        DECLARED_ANNOTATIONS_FIELD.set(f, newDeclaredAnnotations);

    } catch (IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException(e);
    }
  }
}

但是,该字段的声明类没有更新为正确的ReflectionData。所以基本上我需要用它的声明类“安装”新的字段信息,但我很难弄清楚如何。

为了更清楚我的要求,我的测试中的第三个断言失败了:

public class RuntimeAnnotationsTest {

  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.TYPE, ElementType.FIELD})
  public @interface TestAnnotation {}

  public static class TestEntity {
    private String test;
  }

  @Test
  public void testPutAnnotationToField() throws NoSuchFieldException {

    // Confirm class does not have annotation
    TestAnnotation annotation = TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class);
    Assert.assertNull(annotation);

    Field f = TestEntity.class.getDeclaredField("test");
    f.setAccessible(true);

    FieldRuntimeAnnotations.putAnnotationToField(f, TestAnnotation.class, new HashMap<>());

    // Make sure field annotation gets set
    Assert.assertNotNull(f.getAnnotation(TestAnnotation.class));

    // Make sure the class that contains that field is also updated -- THIS FAILS
    Assert.assertNotNull(TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class));
  }

} 

我知道我想要达到的目标相当荒谬,但我很享受这个练习 :D ... 有什么想法吗?

【问题讨论】:

    标签: java reflection


    【解决方案1】:

    使用 TestEntity.class.getDeclaredField("test") 您可以获得 TestEntity.class 的私有内部字段的副本,但您需要原始字段。 我扩展了您的测试用例,从 Class.class 中的私有方法“privateGetDeclaredFields”获取原始私有字段。

    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.Map;
    
    import org.junit.Assert;
    import org.junit.Test;
    
    public class FieldRuntimeAnnotationsTest {
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE, ElementType.FIELD})
        public @interface TestAnnotation {}
    
        public static class TestEntity {
            private String test;
        }
    
        @Test
        public void testPutAnnotationToField() throws NoSuchFieldException {
            // Confirm class does not have annotation
            TestAnnotation annotation = TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class);
            Assert.assertNull(annotation);
    
            // This field is a copy of the internal one
            Field f = TestEntity.class.getDeclaredField("test");
            f.setAccessible(true);
    
            FieldRuntimeAnnotations.putAnnotationToField(f, TestAnnotation.class, new HashMap<>());
    
            // Make sure field annotation gets set
            Assert.assertNotNull(f.getAnnotation(TestAnnotation.class));
    
            // Make sure the class that contains that field is not updated -- THE FIELD IS A COPY
            Assert.assertNull(TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class));
    
            // Repeat the process with the internal field
            Field f2 = getDeclaredField(TestEntity.class, "test");
            f2.setAccessible(true);
    
            FieldRuntimeAnnotations.putAnnotationToField(f2, TestAnnotation.class, new HashMap<>());
    
            // Make sure field annotation gets set
            Assert.assertNotNull(f2.getAnnotation(TestAnnotation.class));
    
            // Make sure the class that contains that field is also updated -- THE FIELD IS THE ORIGINAL ONE
            Assert.assertNotNull(TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class));
        }
    
        public Field getDeclaredField(Class<?> clazz, String name) {
            if (name == null || name.isEmpty()) {
                return null;
            }
            Field[] fields = getDeclaredFields(clazz);
            Field field = null;
            for (Field f : fields) {
                if (name.equals(f.getName())) {
                    field = f;
                }
            }
            return field;
        }
    
        public Field[] getDeclaredFields(Class<?> clazz) {
            if (clazz == null) {
                return new Field[0];
            }
            Method privateGetDeclaredFieldsMethod = null;
            Object value = null;
            try {
                privateGetDeclaredFieldsMethod = Class.class.getDeclaredMethod("privateGetDeclaredFields", boolean.class);
                privateGetDeclaredFieldsMethod.setAccessible(true);
                value = privateGetDeclaredFieldsMethod.invoke(clazz, Boolean.FALSE);
            }
            catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                Assert.fail("Error for " + clazz + ", exception=" + e.getMessage());
        }
            Field[] fields = value == null ? new Field[0] : (Field[])value;
            return fields;
        }
    }
    

    【讨论】:

    • 像魅力一样工作!我错过了使用带有布尔参数false 的字段声明类调用Class.class 方法privateGetDeclaredFields 的关键部分。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 2011-03-04
    • 1970-01-01
    • 2020-11-19
    • 2017-09-03
    相关资源
    最近更新 更多