FieldUtils에서 다루지 않는 몇 가지 단점이 있습니다. 특히 합성 필드 (예 : JaCoCo에 의해 주입 됨) 및 열거 형 유형의 코스에 각 인스턴스에 대한 필드가 있다는 사실과 객체 그래프를 통과하는 경우 모든 필드와 각 필드의 필드 등을 가져 오는 경우 열거 형을 칠 때 무한 루프에 빠지게됩니다. 확장 솔루션 (그리고 솔직히 말하면 이것이 어딘가에 라이브러리에 있어야한다고 확신합니다!)은 다음과 같습니다.
/**
* Return a list containing all declared fields and all inherited fields for the given input
* (but avoiding any quirky enum fields and tool injected fields).
*/
public List<Field> getAllFields(Object input) {
return getFieldsAndInheritedFields(new ArrayList<>(), input.getClass());
}
private List<Field> getFieldsAndInheritedFields(List<Field> fields, Class<?> inputType) {
fields.addAll(getFilteredDeclaredFields(inputType));
return inputType.getSuperclass() == null ? fields : getFieldsAndInheritedFields(fields, inputType.getSuperclass());
}
/**
* Where the input is NOT an {@link Enum} type then get all declared fields except synthetic fields (ie instrumented
* additional fields). Where the input IS an {@link Enum} type then also skip the fields that are all the
* {@link Enum} instances as this would lead to an infinite loop if the user of this class is traversing
* an object graph.
*/
private List<Field> getFilteredDeclaredFields(Class<?> inputType) {
return Arrays.asList(inputType.getDeclaredFields()).stream()
.filter(field -> !isAnEnum(inputType) ||
(isAnEnum(inputType) && !isSameType(field, inputType)))
.filter(field -> !field.isSynthetic())
.collect(Collectors.toList());
}
private boolean isAnEnum(Class<?> type) {
return Enum.class.isAssignableFrom(type);
}
private boolean isSameType(Field input, Class<?> ownerType) {
return input.getType().equals(ownerType);
}
Spock의 테스트 클래스 (그리고 Groovy는 합성 필드를 추가합니다) :
class ReflectionUtilsSpec extends Specification {
def "declared fields only"() {
given: "an instance of a class that does not inherit any fields"
def instance = new Superclass()
when: "all fields are requested"
def result = new ReflectionUtils().getAllFields(instance)
then: "the fields declared by that instance's class are returned"
result.size() == 1
result.findAll { it.name in ['superThing'] }.size() == 1
}
def "inherited fields"() {
given: "an instance of a class that inherits fields"
def instance = new Subclass()
when: "all fields are requested"
def result = new ReflectionUtils().getAllFields(instance)
then: "the fields declared by that instance's class and its superclasses are returned"
result.size() == 2
result.findAll { it.name in ['subThing', 'superThing'] }.size() == 2
}
def "no fields"() {
given: "an instance of a class with no declared or inherited fields"
def instance = new SuperDooperclass()
when: "all fields are requested"
def result = new ReflectionUtils().getAllFields(instance)
then: "the fields declared by that instance's class and its superclasses are returned"
result.size() == 0
}
def "enum"() {
given: "an instance of an enum"
def instance = Item.BIT
when: "all fields are requested"
def result = new ReflectionUtils().getAllFields(instance)
then: "the fields declared by that instance's class and its superclasses are returned"
result.size() == 3
result.findAll { it.name == 'smallerItem' }.size() == 1
}
private class SuperDooperclass {
}
private class Superclass extends SuperDooperclass {
private String superThing
}
private class Subclass extends Superclass {
private String subThing
}
private enum Item {
BIT("quark"), BOB("muon")
Item(String smallerItem) {
this.smallerItem = smallerItem
}
private String smallerItem
}
}