2017-03-08 13 views
0

からthis answerを使用して実行時にフィールドの宣言された注釈を変更すると、新しい内部AnnotationDataオブジェクトを作成してインストールすることにより、実行時に注釈をJavaクラスに追加できます。 Fieldが可能であるかどうか不思議でした。注釈を処理する方法が、とはかなり異なっているようです。Java Reflection 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のprivateメソッド '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