递归修改对象中带有给定注释的所有字段

时间:2018-08-29 17:23:43

标签: java recursion reflection

给定一个输入对象,我需要使用给定的注释查找其中的所有字段,并用一些值对其进行修改。

此注释也可以存在于给定输入对象内部的对象中。

public class A {
  private B b;

  @MyAnnotation
  private String s;

}

public class B {

    @MyAnnotation
    private String a;

    private String b;
}

在这种情况下,输出应为s和a字段,我需要使用一些String对其进行修改。我从apache找到了fieldUtils,它为给定的类提供了注释,我想我可以添加一个简单的DFS搜索来查找具有给定注释的所有字段。使用反射设置值时出现问题,我需要将对象指定为field.set()的一部分。我不确定在设置a的值时应使用哪个对象,以及如何以一般方式获取它(以防在本示例中需要从B检索A的实例)< / p>

3 个答案:

答案 0 :(得分:1)

递归地修改对象中带有给定注释的所有字段,我们必须先确定所有字段

Class.getFields()将仅返回“ 可访问的公共字段”。这不符合要求。因此,我们必须使用Class.getDeclaredFields()。反过来,这仅返回字段“ 由类声明的”。为了获取所有字段,我们必须向上遍历目标对象的类型层次结构,并针对其声明的字段的每种类型进行处理。

对于每个字段,我们都必须区分一些情况:

  1. Field 添加了指定的注释。然后必须将给定值应用于该字段。为了设置该值,必须(使)该字段可访问且非最终字段。
  2. Field 使用指定的注释进行注释。然后有三种情况可以区分:
    • 该字段具有原始类型。然后它不是一个对象,可以包含一个String字段。
    • 该字段具有数组类型。然后它不能包含String字段。
    • 其他:该字段具有引用类型。然后必须(使其)可以访问它,以检查它是否设置了值,在这种情况下,需要对1)和2)进行递归处理。

要检查是否在字段上设置了注释,我们使用Field.getAnnotation(myAnnotationClass),其中myAnnotationClass声明为Class<MyAnnotation>。但是我们必须注意一些其他事项:
Field.getAnnotation(myAnnotationClass)仅在

时返回字段的注释。
  • 已设置(非常不言自明)
  • MyAnnotation本身带有@Retention(RetentionPolicy.RUNTIME)注释。

相应的代码如下所示:

  public static <T extends Annotation> void setValueOnAnnotatedFields(String s, Class<T> annotationClass, Object target) {
    if (target != null) {
      // traversing the type hierarchy upward to process fields declared in classes target's class extends
      Class<?> clazz = target.getClass();
      do {
        setValueOnAnnotatedDeclaredFields(s, annotationClass, target, clazz);
      }
      while ((clazz = clazz.getSuperclass()) != null);
    }
  }

  private static <T extends Annotation> void setValueOnAnnotatedDeclaredFields(String s, Class<T> annotationClass, Object target, Class<?> clazz) {
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
      if (field.getAnnotation(annotationClass) != null) {
        set(field, target, s);
      } else if (!field.getType().isPrimitive() && !field.getType().isArray()) {
        setValueOnAnnotatedFields(s, annotationClass, get(field, target));
      }
    }
  }

  protected static void set(Field field, Object target, String value) {
    try {
      makeFiieldAccessibleNotFinal(field, target);
      field.set(target, value);
    }
    catch (Exception e) {
      String message = String.format("Failed to set the value on field %s", target.getClass().getSimpleName() + "." + field.getName());
      throw new IllegalStateException(message, e);
    }
  }

  protected static Object get(Field field, Object target) {
    try {
      makeFiieldAccessibleNotFinal(field, target);
      return field.get(target);
    }
    catch (Exception e) {
      String message = String.format("Failed to get the value on field %s", target.getClass().getSimpleName() + "." + field.getName());
      throw new IllegalStateException(message, e);
    }
  }

  protected static void makeFiieldAccessibleNotFinal(Field field, Object target) {
    try {
      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    }
    catch (Exception e) {
      String message = String.format("Failed to remove final declaration of field %s", field.getName());
      throw new IllegalStateException(message, e);
    }
    try {
      field.setAccessible(true);
    }
    catch (Exception e) {
      String message = String.format("Failed to make field %s accessible", field.getName());
      throw new IllegalStateException(message, e);
    }
  }

一个例子:

  public static void main(String[] args) {
    A a = new A();
    System.out.println(a); // s: null / b: [null]

    setValueOnAnnotatedFields("1", MyAnnotation.class, a);
    System.out.println(a); // s: 1 / b: [null]

    a.b = new B();
    setValueOnAnnotatedFields("2", MyAnnotation.class, a);
    System.out.println(a); // s: 2 / b: [a: 2 / b: null]
  }

这意味着类A的实现toString()是这样的:

@Override
public String toString() {
  return "s: " + s + " / b: [" + Optional.ofNullable(b).map(B::toString).orElse("null") + "]";
}

并且将B类实现为toString(),如下:

@Override
public String toString() {
  return "a: " + a + " / b: " + b;
}

答案 1 :(得分:0)

您可以使用Java Reflection API来检查您的类的字段:

获取每个字段的声明的注释;

检查您是否有注释。

好吧,感谢Reflection API,这并非难事。

答案 2 :(得分:0)

您可以在必须向下钻取的字段上添加一些标记注释。 Bean验证使用类似的方法。

下面,Bar嵌套在Foo中,并用DrillDown进行注释,以指示必须将其钻取,以查找要更新的字段。

class Foo {
  @Updateable String a;
  @DrillDown Bar bar;
  String c;
}

class Bar {
  @Updateable String b;
}

演示它的快速示例代码,

import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.annotation.Retention;

class Main {
  public static void main(String[] args) throws Exception {
    Foo foo = new Foo();
    foo.a = "a-old";
    foo.c = "non-updateable";
    Bar bar = new Bar();
    bar.b = "b-old";
    foo.bar = bar;

    update("new-value", foo);

    System.out.println(foo.a);
    System.out.println(foo.bar.b);
    System.out.println(foo.c);
  }

  public static void update(String value, Object obj) throws Exception {
    for (Field field : obj.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      Updateable updateable = field.getAnnotation(Updateable.class);
      if (updateable != null) {
        field.set(obj, value);
      } else {
        DrillDown drillDown = field.getAnnotation(DrillDown.class);
        if (drillDown != null) {
          update(value, field.get(obj));
        }
      }
    }
  }
}

class Foo {
  @Updateable String a;
  @DrillDown Bar bar;
  String c;
}

class Bar {
  @Updateable String b;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DrillDown {

}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Updateable {

}