메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Java 리플렉션에 대한 재고(reflection)(2)

한빛미디어

|

2007-05-07

|

by HANBIT

27,814

제공 : 한빛 네트워크
저자 : Russ Olsen
역자 : 백기선
원문 : Reflections on Java Reflection

클래스를 좀 더 자세히 살펴보기

첫 번째 예제에서 봤듯이 Class 객체는 그것의 이름이나 상위 클래스 같은 정보를 제공합니다. 이 이름을 사용하여 좀 더 자세히 rank 와 시리얼 넘버(serial number) 차원의 정보까지 알 수 있습니다. 예를 들어 getMethods 메소드를 사용하여 클래스가 가진 모든 public 메소드를 찾을 수 있습니다.
      Class klass = Class.forName("com.russolsen.reflect.Employee");
           
      Method[] methods = klass.getMethods();
      
      for(Method m : methods )
      {
         System.out.println( "Found a method: " + m );
      }
getMethods 는 클래스가 가지고 있는 public 메소드 각각에 해당하는 Method 객체의 배열을 반환합니다.
Found a method: public java.lang.String com.russolsen.reflect.Employee.toString()
Found a method: public int com.russolsen.reflect.Employee.getSalary()
Found a method: public void com.russolsen.reflect.Employee.setSalary(int)
Found a method: public native int java.lang.Object.hashCode()
Found a method: public final native java.lang.Class java.lang.Object.getClass()
Found a method: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
Found a method: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
Found a method: public final void java.lang.Object.wait() throws java.lang.InterruptedException
Found a method: public boolean java.lang.Object.equals(java.lang.Object)
Found a method: public java.lang.String java.lang.Object.toString()
Found a method: public final native void java.lang.Object.notify()
Found a method: public final native void java.lang.Object.notifyAll()
getMethods는 클래스의 사용자(client) 입장이기 때문에, 배열에는 자신이 가지고 있는 모든 public 메소드 (Object 까지 달하는 모든 상속 계층의 상위 클래스들에 있는 public 메소드까지 포함하여)를 배열에 담아 줍니다.

만약 하나의 메소드에만 관심이 있다면, getMethod(단수 형태 입니다.)를 사용할 수 있습니다. getConstructor와 비슷하게 동작하지만 파라미터 타입들 뿐만 아니라 메소드의 이름도 넘겨 줘야 합니다. 아래에 있는 코드는 하나의 int 파라미터를 가지는 setSalary 라는 메소드를 찾습니다.
      Class klass = Class.forName("com.russolsen.reflect.Employee");
      Class[] paramTypes = {Integer.TYPE };
      Method setSalaryMethod = 
         klass.getMethod("setSalary", paramTypes);
           
      System.out.println( "Found method: " + setSalaryMethod);
리플렉션을 사용하여 메소드를 호출하는 것은 constuctor를 호출하는 것과 매우 유사합니다. 앞서 살펴봤던 Method 객체만 있으면 되고 메소드에 인자로 넘겨 줄 배열과 메소드를 호출할 객체가 필요합니다. 아래에 있는 코드는 우리 직원에게 월급을 올려주기 위해서 Employee 객체에 있는 setSalary 메소드를 호출합니다.
      Class klass = Class.forName("com.russolsen.reflect.Employee");

      Class[] paramTypes = {Integer.TYPE };
      Method setSalaryMethod = 
         klass.getMethod("setSalary", paramTypes);
      
      Object theObject = klass.newInstance();
      Object[] parameters = { new Integer(90000) };
      
      setSalaryMethod.invoke(theObject, parameters);
그냥 theObject.setSalary(9000)를 호출하지 않고 왜 그렇게 귀찮게 했을까요? 위에 있는 코드를 자세히 살펴보시길 바랍니다. 코드의 첫 번째 줄을 빼면 위에 있는 프로그램은 완전히 일반적입니다. 어떤 클래스의 어떤 객체든 상관없이 setSalary 메소드를 호출할 것입니다. 약간만 수정하면 어떤 클래스의 어떤 객체든 거기에 있는 모든 메소드를 호출하는 코드로 사용할 수 있습니다.

필드 가지고 놀기

리플렉션을 사용하여 메소드를 호출하는 데에서 그치지 않습니다. 필드에 대한 모든 권한 역시 가지고 있습니다. getMethods 처럼 getFields는 해당 클래스 또는 그것의 상위 클래스에 있는 모든 public 필드 각각에 대한 Fileld 객체의 배열을 반환합니다.
      Class klass = Class.forName("com.russolsen.reflect.Employee");
      
      System.out.println( "Class name: " + klass.getName());
      
      Field[] fields = klass.getFields();
      
      for(Field f : fields )
      {
         System.out.println( "Found field: " + f);
      }
Employee 가 두 개의 public 필드를 가지고 있기 때문에 두 개의 멤버를 가진 배열을 얻게 됩니다.
Class name: com.russolsen.reflect.Employee
Found field: public java.lang.String com.russolsen.reflect.Employee._firstName
Found field: public java.lang.String com.russolsen.reflect.Employee._lastName
getField메소드를 사용하여 특정 필드 하나만 가져올 수도 있습니다.
      Field field = klass.getField("_firstName");
      System.out.println("Found field: " + field);
Field 객체를 가지고 get 메소드를 호출하면 필드가 가진 값을 가져올 수 있고 set을 사용하여 값을 설정할 수 있습니다.
      Object theObject = new Employee("Tom", "Smith", 25);
      
      Class klass = Class.forName("com.russolsen.reflect.Employee");
      
      Field field = klass.getField("_firstName");
      
      Object oldValue = field.get(theObject);
      System.out.println( "Old first name is: " + oldValue);
      
      field.set( theObject, "Harry");
      Object newValue = field.get(theObject);
      System.out.println( "New first name is: " + newValue);
위에 있는 코드를 실행하고 _firstName 필드가 변하는 것을 주의 깊게 살펴보시기 바랍니다.
Old first name is: Tom
New first name is: Harry
규칙 깨기

Java의 가장 신성한 규칙 중 하나를 깨는 방법을 얘기하지 않고서는 리플렉션을 마무리할 수 없습니다. 여러분 모두 잘 알다시피 해당 클래스 밖에서는 private 메소드를 호출 할 수 없습니다. 그렇죠? 아마 평범한 기술을 사용하는데 그친다면 하지 못할 것입니다. 하지만 리플렉션을 사용하면 거의 모든 걸 할 수 있습니다.

private 메소드를 호출하기 위해서 가장 먼저 해야 할 일은 호출하고 싶어 하는 메소드를 나타내는Method객체를 얻는 것입니다. getMethod로는 얻을 수 없습니다. 그건 오직 public 메소드만 반환합니다. private(또는 protected)메소드를 가져오는 방법은 getDeclaredMethod를 사용하는 것입니다. getMethod는 해당 클래스의 사용자 관점(client’s view)에서 public 메소드만 가져오지만, getDeclaredMethod는 클래스에 선언한 모든 메소드를 반환합니다. 아래에 있는 예제에서 java.util.ArrayList 클래스에 있는 private 메소드인 removeRange 를 가져옵니다.
      ArrayList list = new ArrayList();
      list.add("Larry");
      list.add("Moe");
      list.add("Curley");

      System.out.println("The list is: " + list);

      Class klass = list.getClass();

      Class[] paramTypes = { Integer.TYPE, Integer.TYPE };
      Method m = klass.getDeclaredMethod("removeRange", paramTypes);

      Object[] arguments = { new Integer(0), new Integer(2) };
      m.setAccessible(true);
      m.invoke(list, arguments);
      System.out.println("The new list is: " + list);
private 메소드를 받은 뒤에 간단히 setAccessable 안전장치를 제거하고 호출하면 됩니다.
The list is: [Larry, Moe, Curley]
The new list is: [Curley]
removeRange 메소드는 리스트에서 주어진 범위의 아이템을 제거하는 것처럼 보입니다. 이것은 매우 강력한 마술입니다. java.util 에 있는 클래스에 접근하여 그 안에 있는 private 메소드를 호출할 수 있습니다. 이것을 사용하여 코드의 의도를 우회하여 private 메소드를 호출하는 취미를 즐기실 건가요? 아니죠! 그러나 저런 것들이 약간은 유용할 때가 있습니다.

결론

리플렉션을 사용하여 Java의 규칙을 무시하는 것처럼 보이는 것들을 하는 프로그램을 작성할 수 있습니다. 일반적으로는 알 수 없는 클래스에 관한 정보를 모두 알아내는 코드를 작성할 수 있습니다. 그리고 동적으로 알아낸 정보에 어떤 행위를 할 수도 있습니다. 즉 새로운 객체를 만들고 메소드를 호출하고 필드에 값을 설정하거나 가져올 수 있습니다. 극단적인 경우에서는 클래스의 private 멤버들에 접근할 수 있습니다. 점점 복잡해져가는 Java 개발 툴의 동작을 이해하기 위해서는 리플렉션에 대한 이해가 필요합니다. 또한 “평범한” Java 프로그램이 할 수 있는 것 이상의 프로그램을 작성해야 할 때도 필요합니다.

Resources
저자 Russ Olsen은 현재 FGM의 선임 엔지니어로 J2EE와 Rails로 정보 시스템을 구축하고 있다. Russ는 자유로운 시간 중 대부분을 기술에 관한 글을 쓰는데 소비한다.
역자 백기선님은 AJN(http://agilejava.net)에서 자바 관련 스터디를 하고 있는 착하고 조용하며 점잖은 대학생입니다. 요즘은 특히 Spring과 Hibernate 같은 오픈소스 프레임워크를 공부하고 있습니다. 공부한 내용들은 블로그(http://whiteship.tistory.com)에 간단하게 정리하고 있으며 장래 희망은 행복한 개발자입니다.
TAG :
댓글 입력
자료실