2017-03-08 89 views
0

this answer可以通過創建和安裝新的內部對象AnnotationData在運行時向Java類添加註釋。我很好奇Field是否可能。看起來Field處理註釋的方式與Class處理註釋的方式有很大不同。使用Java反射API在運行時修改字段的聲明註釋

我已經能夠註釋成功添加到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 ...有什麼想法?

回答

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; 
    } 
} 
+0

工程就像一個魅力!我錯過了調用'Class.class'方法'privateGetDeclaredFields'的關鍵部分,該方法聲明瞭帶有布爾型參數'false'的類。謝謝! – heez