JUnit 테스트 코드를 작성하다 보면 특정 클래스의 private 필드나 메서드에 접근해야 할 때가 있는데,
이때 Getter나 Setter가 없는 경우가 종종 있다.
이런 상황에서는 자바의 리플렉션(Reflection) 기능을 활용해 private 필드와 메서드에 접근할 수 있다.
◆ 리플렉션(Reflection)이 뭐야?
자바 리플렉션은 프로그램 실행 중에 클래스의 구조와 정보를 동적으로 조회하고, 필드나 메서드, 생성자 등에 접근하거나 실행할 수 있게 해주는 강력한 기술이다.
하지만 리플렉션은 강력한 점에 비해 일반 코드에 비해 실행 속도가 느릴 수 있고, 런타임 오류가 발생할 가능성도 높으며, 자바의 캡슐화 원칙을 무너뜨릴 수 있다는 단점이 있기 때문에 테스트 코드나 프레임워크 개발처럼 꼭 필요한 경우가 아니라면, 일반적인 로직에서는 리플렉션 사용을 가급적 자제하는 것이 좋다.
◆ 리플렉션(Reflection)을 활용해 private 필드에 접근하기 (예제)
package test;
public class Form {
private String name = "홍길동";
private int age = 20;
}
public class Test {
public static void main(String[] args) {
try {
Form form = new Form();
Field nameField = form.getClass().getDeclaredField("name");
nameField.setAccessible(true);
System.out.println("before: " + nameField.get(form));
nameField.set(form, "김길동");
System.out.println("after: "+ nameField.get(form));
} catch (Exception e) {
e.printStackTrace();
}
}
}
위 코드는 리플렉션(Reflection)을 활용해 private 필드값을 변경하는 예제이고, 이 예제를 바탕으로 상세설명을 이어가겠다.
▶ Form form = new Form();
우선 Form 클래의 private 필드에 접근해야 하기 때문에, Form 클래스의 인스턴스를 하나 생성한다.
▶ Field nameField = form.getClass().getDeclaredField("name");
form.getClass() 를 통해 현재 객체의 실제 클래스 정보를 가져오고,
.getDeclaredField("name") 를 통해 클래스에 선언된("declared") 이름이 name인 필드를 가져온다.
(private, protected이든 public이든, 해당 이름의 필드 정보를 가져옴)
아래 3가지 방식 모두 필드를 가져오는 것이 가능하다.
Form form = new Form();
Field nameField = form.getClass().getDeclaredField("name");
Form form = new Form();Field nameField = Form.class.getDeclaredField("name");
Class<Form> form = Form.class
Field nameField = form.getDeclaredField("name");
▶ nameField.setAccessible(true);
private 필드에도 접근이 가능하도록 접근 제한을 해제한다.
원래는 private이라 직접 접근 불가지만, 리플렉션에서는 이걸 true로 설정하면 강제로 접근 가능 해 진다.
이 설정을 하지 않으면 아래와 같이 IllegalAccessException 이 발생한다.
▶ nameField.set(form, "김길동");
form 객체의 name 필드 값을 변경한다. ("홍길동" ⇒ "김길동")
당연한 것이지만, 필드 값을 설정할 때는 private 필드의 타입과 같은 타입의 값을 넣어 줘야 한다.
예를 들어 name 필드의 경우 String 타입인데, nameField.set(form, 50); 이런 식으로 int값을 지정하면
아래와 같이 IllegalArgumentException 이 발생한다.
▶ nameField.get(form)
form 객체가 가진 private String name의 현재 값을 리플렉션으로 꺼내온다.
해당 예제 코드의 결과를 보면 "홍길동"이었던 name 필드의 값이 변경한 값인 "김길동"으로 적용된 것을 확인할 수 있다.
◆ 리플렉션(Reflection)을 활용해 private 메서드에 접근하기 (예제)
package test;
public class Form {
private void sayHello() {
System.out.println("Hello!");
}
}
package test;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
try {
Form form = new Form();
Method method = Form.class.getDeclaredMethod("sayHello");
method.setAccessible(true);
method.invoke(form);
} catch (Exception e) {
e.printStackTrace();
}
}
}
위 코드는 리플렉션(Reflection)을 활용해 private 메서드에 접근하는 예제이고, 이 예제를 바탕으로 상세설명을 이어가겠다.
▶ Form form = new Form();
우선 Form 클래의 private 메서드에 접근해야 하기 때문에, Form 클래스의 인스턴스를 하나 생성한다.
▶ Method method = Form.class.getDeclaredMethod("sayHello");
Form 클래스에 선언된 "sayHello"라는 이름의 메서드 정보를 리플렉션으로 가져온다.
(private, protected이든 public이든, 해당 이름의 필드 정보를 가져옴)
▶ method.setAccessible(true);
필드와 마찬가지로 private 메서드도 접근 가능하도록 제한을 해제한다.
▶ method.invoke(form);
form 객체에서 해당 메서드(sayHello)를 실행(호출)한다.
invoke의 첫 번째 인자는 메서드를 호출할 인스턴스이고, 만약 호출할 private 메서드에 매개변수(파라미터)가 있다면
invoke의 두 번째 인자부터 차례대로 넣어주면 된다.
참고로 리플렉션의 invoke() 자체로는 "그 메서드가 실제로 리턴하는 값"을 가져올 뿐 메서드의 리턴값을 바꿀 수 없다.
만약, 테스트에서 임의로 리턴값을 바꾸고 싶다면 JUnit + Mockito, JMockit, PowerMock 등의 mocking 프레임워크를 사용해야 한다.
이 경우, 실제로 메서드 내부를 실행하지 않고, 원하는 값이 리턴되게 할 수 있다.
참조
Using Java Reflection
https://www.oracle.com/technical-resources/articles/java/javareflection.html
getDeclaredField
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getDeclaredField(java.lang.String)
글 내용 중 잘못된 부분이 있거나, 첨부하실 내용이 있으시면 댓글로 남겨주세요. 공부하는데 많은 도움이 됩니다.
-- 기억의 유효기간은 생각보다 짧다. --
'주요 언어 > JAVA' 카테고리의 다른 글
HTTP GET 요청시 java.net.URISyntaxException 에러 해결 방법 (0) | 2025.01.13 |
---|---|
HttpServletRequest에서 클라이언트의 IP 주소 추출하기 (0) | 2024.11.01 |
명령 프롬프트(CMD) 에서 자바(.java)파일 컴파일 후 실행 (1) | 2024.08.28 |
Object를 String으로 변환 (valueOf, Casting, toString) (0) | 2024.03.01 |
숫자를 언더바_(Underscore)와 같이 표시하기 (2) | 2024.03.01 |