본문 바로가기
Today I Learned 2024. 6. 18.

(24.06.18)[10주차] Reflection & ReflectionTestUtils을 통한 필드 다루기

@Entity
@Getter
@Table(name = "users")
public class User {
...
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true, nullable = false, name = "username")
    private String username;
    @Column(nullable = false)
    private String password;
    @Column(nullable = false)
    private String name;
    @Column(unique = true, nullable = false)
    private String email;
    @Column(name = "user_info")
    private String userInfo;
    @Column(nullable = false)
...

 

  • 위 코드는 @Column의 필드가 private으로 접근제어자로 설정된 임의의 Entity 의 일부
  • Setter 또는 오버라이드된 생성자를 통해 캡슐화된 상수들의 값을 설정할 수있는데 위의 엔터티에서는 존재하지 않음
    • 실제 로직을 구성하는 코드에는 상수 값에 대한 설정이 사용되지는 않지만, Test Code 등에 임의로 생성이 필요할 때가 존재 -> 그렇다면 @Setter @Builder 또는 생성자를 사용하지 않고 사용하는 방법으로 Reflection 기능을 사용

Reflection  &  ReflectionTestUtils을 통한 필드 다루기

Reflection

  • Java의 기본 기능 중 Runtime 시점에서 클래스에 직접적으로 확인 및 조작하는 데 사용하는 기능
    • 객체의 생성 & 구조를 분석 하는데 사용
    • 그러므로 유연성 있게 사용이 가능
  • 접근제어자 상관 없이 직접적으로 클래스단위에서 조작에 대해서는 Runtime 시점에서 치명적인 오류가 발생가능성이 있음
    • 그러므로 디버깅을 통해서 Reflection으로 인한 오류 발생을 잡을 수 없음

Reflection 클래스 단위에서의 주요 기능

  • getClass()
    • 생성된 객체로 해당 클래스를 얻는 메서드
      • Object obj = new String() 로 선언이 되었다면 obj.getClass() 일 때, String 을 반환
  • Class.forName("클래스이름")
    • 해당 클래스이름(또는 패키지)에 해당하는 클래스를 동적으로 로드하고 "클래스이름".class  형태의 Class 객체를 반환하는 메서드
      • .class
        • 클래스 리터럴  Class Literal로 컴파일할때 그 타입을 나타내는데 사용하는 것과 동일 한 것으로 Class 객체를 반환시킬 때 Java 코드내에서도 직접적으로 상용할 수 있음
        • 얻어진 Class 객체에서는 클래스에 정의된 멤버 이름, 갯수 파악, 클래스 객체를 통한 객체 생성(.newInstance() 메서드) 및 내부 메서드 호출 모든 것이 가능해짐

Reflection 필드 단위에서의 주요 기능

MyClass myClass = new MyClass();

Field field = MyClass.class.getDeclaredField("name");

Object fieldValue = field.get(myClass);
field.setAccessible(true); 
field.set(myClass, "클래스")
  • getDeclaredField("필드이름")
    • 클래스 객체에서 해당 필드이름을 가진 정보를 가지고 올 수 있는 매서드
      • Field field = User.class.getDeclaredField("id"); 일 경우, User 클래스에서 id 필드를 가지고 오는 것
  • (필드객체).get(대상클래스객체)
    • getDeclareField로 만들어진 Field 객체를 통해 필드값을 대상 클래스 객체에선 찾아서 반환하는 메서드
  • (필드객체).set(대상클래스객체, "해당필드에 설정할 값")
    • getDeclareField로 만들어진 Field 객체에서 설정하고 싶은 값을 해당 클래스에서 설정할 수 있는 메서드
    • 단, private 접근제어자 일 경우에는 .setAccessible(true) 를 꼭 붙일 수 있도록 해야함

ReflectionTestUtil

  • Java의 Spring Framework 에서 제공되어지는 클래스로 Reflection의 기능을 대신 할 수 있도록 하는 클래스
  • 특히, Test Code내에서 특정 private 접근제어자 필드에 대해서 설정을 하는데 주요 사용
    • 테스트 대상 클래스의 내부 필드를 조정하는 기능
  • 복잡한 Reflection의 구조, 오류발생 여부, 오래 걸리는 작동 시간 등을 고려했을 때, Spring 환경에서의 테스트할 때는 Reflection  기본 보다는 RelfectionTestUtil을 사용할 수 있도록 해야

ReflectionTestUtil 주요 기능

MyClass myClass = new MyClass();

String name = (String) ReflectionTestUtils.getField(myClass, "name");

ReflectionTestUtils.setField(myClass, "name", "클래스");
  • .getField(대상클래스객체, "값을 얻어올 필드이름")
    • 특정 클래스 객체에서 특정 필드의 값을 얻어올 수 있는 메서드
  • .setField(대상클래스객체, "값을 지정할 필드이름", 지정할값)
    •  특정 클래스 객체에서 특정 필드의 값을 지정할 수 있는 메서드
      •  접근제어자가 private인 필드도 당연히 사용이 가능
    • 원래는 .getField(대상클래스객체, 해당클래스타입(클래스 리터럴), "값을 지정할 필드이름 ", 지정할값, 필드클래스타입(클래스리터럴) ) 이지만 Java가 작동할 때 이미, 해당 객체를 불러오면서 타입을 이미 알고 있기 때문에, 굳이 쓰지 않아도 됨
 
 

자바의 정석에서도 Reflection API 정도로만 간단히 소개가 되어서 실제로 Test Code를 사용하면서 따로 찾아보면서 학습을 해야하는 부분이라 꼼꼼하게 찾아보면서 정리를 했다.