2011년 10월 18일 화요일

private 접근자를 가진 객체의 필드에 접근하기.

가끔 오픈 소스나 라이브러리를 가지고 개발해야 하는 경우에 특정 객체의 필드에 접근해야 할 필요가 있다.
아래의 샘플 코드를 살펴 보자.

package test.java;

import java.util.HashMap;
import java.util.Map;

public class InsertableContainer {

  private Map map = new HashMap();

  public InsertableContainer() {
    map.put("firstName", "Younggyu");
    map.put("lastName", "Kim");
    map.put("age", "25");
  }
  
  public void insert(String key, String value) {
    if(map.containsKey(key)) {
      throw new IllegalArgumentException("key already exists: " + key);
    }
    map.put(key, value);
  }
  
  public void display() {
    System.out.println(map);
  }
}

위의 클래스는 프로퍼티를 추가하는 insert 메서드와 설정된 정보를 출력하는 display 메서드만 제공하는 간단한 클래스다.
그런데 insert 메서드에서는 이미 키가 존재하는 경우에 IllegalStateException을 발생시키기 때문에 기존에 설정된 프로퍼티값을 변경할 수가 없다.
InsertableContainer를 설계할 당시에는 평생 25살로 살거라 생각했었는데, 어느덧 35살이 되었기 때문에 age 속성을 변경해야할 필요가 생겼다.
public 접근자를 가진 메서드만 가지고는 age를 수정시킬 방법이 없다.
아래의 테스트 코드는 IllegalArgumentException을 발생시킨다.

  @Test(expected=IllegalArgumentException.class)
  public void testField() {
    InsertableContainer container = new InsertableContainer();
    container.display();
    
    container.insert("age", "35");
  }
이런 경우에 Reflection을 이용하여 InsertableContainer의 map 필드를 읽어 내서 수정하는 방법을 고려해 보자.
아래의 소스 코드를 살펴보자.
리플렉션을 이용하여 InsertableContainer의 private 접근자를 가진 'map' 필드를 읽어 내고 있다.
  @SuppressWarnings("unchecked")
  private Map getContainerMap(InsertableContainer container) {
    Map map = null;

    try {
      Field field = InsertableContainer.class.getDeclaredField("map");
      field.setAccessible(true);
      map = (Map)field.get(container);
    } catch(Exception ex) {
      ex.printStackTrace();
    }
    return map;
  }
위의 소스 코드에서 field.setAccessible(true); 소스 코드가 private 필드에 접근이 가능하게 한다.
accessible을 false 로 지정하거나, 아예 지정하지 않으면 private Field에 접근할 수 없기 때문에 아래와 같이 오류가 발생한다.

"java.lang.IllegalAccessException: Class test.java.FieldTest can not access a member of class test.java.InsertableContainer with modifiers "private"".

테스트 소스 코드를 아래와 같이 수정하면 이제 오류가 발생하지 않고 정상적으로 동작한다.
  @Test()
  public void testField2() {
    InsertableContainer container = new InsertableContainer();
    container.display();

    Map map = getContainerMap(container);
    map.put("age", "35");
    
    container.display();
  }

화면 출력은 아래와 같다.

{lastName=Kim, age=25, firstName=Younggyu}
{lastName=Kim, age=35, firstName=Younggyu}

댓글 없음:

댓글 쓰기