[자바] 리플렉션으로 상속받은 field 값 구하기

최대한·2021년 6월 28일
2

개요


  • 프로젝트의 코드 컨벤션은 따로 존재하지 않음 🙅
  • 수많은 종류의 DTOEntity 로 옮기는 로직을 리팩토링 💦
  • 여러명 이 여러 DTO 에 대해 작업
  • 어떤 클래스는 BaseDto, 어떤 클래스는 Entity 를 상속 받는 등 일관성이 없음
  • 하지만 모두 id 라는 필드를 가지고 있음

따라서 추상화되지 않은 클래스들로부터 같은 타입, 같은 필드명을 가진 필드로부터 값을 추출해내기 위해 리플렉션을 사용하게 되었다.

기술 스택 >>

JDK 8

본문

1. BaseEntity.java

class BaseEntity {
    private Long id;

    public BaseEntity(Long id) {
        this.id = id;
    }
}

2. User.java

class User extends BaseEntity {
    private String name;
    private String email;

    public User(Long id, String name, String email) {
        super(id);
        this.name = name;
        this.email = email;
    }
}

3. main

public static void main(String[] args) {
        User vitamax = new User(1L, "Vitamax", "vitamax@foo.com");
        try {
            Field field = vitamax.getClass().getDeclaredField("id");
	    field.setAccessible(true);
            System.out.println(field.get(vitamax));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
}

결과는 ?

java.lang.NoSuchFieldException: id
at java.lang.Class.getDeclaredField(Class.java:2070)
at com.daehan.test.ReflectionApi.main(ReflectionApi.java:14)

결과는 위와 같이 id 라는 필드값이 없다는 익셉션이 발생하였다.

이유는 간단하다. 말 그대로 id 라는 필드는 User 에 속한 것이 아닌 상위 클래스
BaseEntity 에 있기 때문.

해결방안은 해당 클래스부터 모든 superClass 를 순회하고 해당 필드를 가져오는 것이다.

4. Reflection methods

    public static <T> List<Field> getAllFields(T t){
        Objects.requireNonNull(t);

        Class<?> clazz = t.getClass();
        List<Field> fields = new ArrayList<>();
        while(clazz != null){	// 1. 상위 클래스가 null 이 아닐때까지 모든 필드를 list 에 담는다.
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }
    
    public static <T> Field getFieldByName(T t, String fieldName){
        Objects.requireNonNull(t);

        Field field = null;
        for(Field f : getAllFields(t)){
            if (f.getName().equals(fieldName)){
                field = f;	// 2. 모든 필드들로부터 fieldName이 일치하는 필드 추출
                break;
            }
        }
        if (field != null){
            field.setAccessible(true);	// 3. 접근 제어자가 private 일 경우
        }
        return field;
    }
    
    public static <T> T getFieldValue(Object obj, String fieldName){
        Objects.requireNonNull(obj);

        try {
            Field field = getFieldByName(obj, fieldName); // 4. 해당 필드 조회 후
            return (T) field.get(obj);	// 5. get 을 이용하여 field value 획득
        } catch (IllegalAccessException e){
            return null;
        }
    }

5. main

    public static void main(String[] args) {
        User vitamax = new User(53L, "Vitamax", "vitamax@foo.com");
        Long userId = getFieldValue(vitamax, "id");
        System.out.println("userId = " + userId);
    }

결과는 ?

userId = 53

위와같이 상위 클래스들의 모든 필드들을 조회한 뒤 필드명을 검색하니 정상적으로 값을 가져올 수 있었다.

결론

위에서 다뤘던 것처럼 동일한 필드명을 갖는 경우에 대해, 리플렉션을 이용하여 상위 클래스들도 조회 후 가져올 수 있었다. 하지만 이는 애초에 동일한 클래스를 상속받지 않고 처리했기 때문에 나타난 결과이다. 따라서 가급적 처음부터 dto 등의 클래스 설계를 잘 하고 간다면 굳이 리플렉션이라는 불필요한 작업을 하지 않아도 된다.
추가적으로 하지만 같은 클래스 & 같은 필드에 대해 똑같은 작업이 반복될 경우 Dynamic Programming 을 이용한다면 굳이 while 문을 돌면서 리소스를 낭비할 필요는 없어보인다. 이는 추후에 시간이 되면 수정해 봐야겠다.

profile
Awesome Dev!

2개의 댓글

comment-user-thumbnail
2021년 7월 13일

고생하셨네요!

1개의 답글