본문 바로가기

Language/Java

Java Reflection 개념 및 사용법

Java Reflection 정의


리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다. 투영, 반사 라는 사전적인 의미를 지니고 있다.


스프링을 공부하다가 보면 BeanFactory 라는 Spring Container 개념을 학습하게 된다.

이 BeanFactory는 어플리케이션이 실행한 후 객체가 호출 될 당시 객체의 인스턴스를 생성하게 되는데 
그 때 필요한 기술이 Reflection이다.
자바는 스크립트 언어가 아닌 컴파일 언어이다. 물론 .java -> .class -> 실행이라는 2단계의 메커니즘을 가지고 있지만 컴파일 언어로 분리하는 게 옳다. 원래 자바에서는 동적으로 객체를 생성하는 기술이 없었다. 그리고 동적으로 인스턴스를 생성하는 Reflection으로 그 역활을 대신하게 된다.

리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다. 투영, 반사 라는 사전적인 의미를 지니고 있다

가정을 해 보자. 만약 객체의 메모리만을 알고 있고, 그리고 객체의 형에 대해서는 모른다고 생각하보자, 

리플렉션으로 형은 알고 있지만 형변환을 할 수 없는 상태에서 객체의 메서드를 호출할 수 있다.



Class c = Data.class;
//Class c = Class.forName("클래스이름");

Method[] m = c.getMethods();                     

Field[] f = c.getFields();
Constructor[] cs = c.getConstructors();
Class[] inter = c.getInterfaces();
Class superClass = c.getSuperclass();



Java Reflection 의 사용

 

 

reflection 은 자바의 특징이다. 실행중인 자바프로그램 내부를 검사하고 내부의 속성을 수정할 수 있도록 한다. 예를 들어, 어떤 자바클래스가 가진 모든 멤버의 이름을 얻거나 보여줄 수 있다.
자바에서 클래스가 그 자신을 조사하고 수정하는 것이  많다고 할수는 없으나 다른 언어에서는 볼수 없는 특징이다.

reflection 이 구체적인 쓰임중에 하나가 빌더툴을 이용해서 소프트웨어 콤포넌트를 만드는 곳에서 이다. 툴은 reflection 을 사용해서 동적으로 로딩되는 자바 콤포넌트(클래스)의 속성을 얻을 수 있다.

 

예제

import java.lang.reflect.Method;


public class DumpMethods {

public static void main(String args[]) {

try {

Class c = Class.forName(args[0]);

Method m[] = c.getDeclaredMethods();

for (int i = 0; i < m.length; i++)

System.out.println(m[i].toString());

} catch (Throwable e) {

System.err.println(e);

}

}

}

 

명령

java DumpMethods java.util.Stack

 

실행 결과

  public java.lang.Object java.util.Stack.push(
    java.lang.Object)
   public synchronized 
     java.lang.Object java.util.Stack.pop()
   public synchronized
      java.lang.Object java.util.Stack.peek()
   public boolean java.util.Stack.empty()
   public synchronized 
     int java.util.Stack.search(java.lang.Object)
 
이 프로그램은 java.util.Stack 의 속성과 반환값에 따라 메소드리스트를 출력한다.
이 프로그램은 Class.forName 을 통해서 클래스를 로딩하고 getDeclaredMethods 을 통해서 클래스에서 정의한 메소드리스트를 얻는다. java.lang.reflect.Method  는 단일 메쏘드를 나타내는 클래스이다.

 

Reflection 을 사용한 Set up
Method 와 같은 reflection class 는 java.lang.reflect 에 있다. 이 클래스를 사용하기 위해서는 세가지 스텝을 밟아야 한다.
첫번째는 수정하기를 원하는 클래스의 java.lang.Class 객체를 얻어야 한다. java.lang.Class 는 클래스를 표현하고, 실행중인 자바 프로그램과 인터페이스한다.


자바기본형에 대한 클래스 정보를 얻는 방법.
방법 1 : 
   Class c = Class.forName("java.lang.String");


방법 2 : 
   Class c = int.class;


방법 3 : 기본형의 경우 (Integer 와 같은) Wrapper 에 기정의된 TYPE을 사용한다. 
  Class c = Integer.TYPE;


두번째 스텝은 getDeclaredMethods와 같은 메소드를 Call 해서, 클래스에 정의된 모든 메소드의 리스트를 얻는다.

세번째 스텝은 정보 수정을 위해 Reflection API 를 이용한다.

   Class c = Class.forName("java.lang.String");
   Method m[] = c.getDeclaredMethods();
   System.out.println(m[0].toString());

String 에 선언된 첫번째 메소트를 보여준다.


 

Simulating the instanceof Operator
클래스 정보가 있으면, 다음 스텝은 클래스 객체에 대해서 기본적인 질의다. Class.isInstance 메쏘드는 instanceof 를 통해서 구현될 수 있다.


class A {}


public class instance1 {

public static void main(String args[]) {

try {

Class cls = Class.forName("A");

boolean b1 = cls.isInstance(new Integer(37));

System.out.println(b1);

boolean b2 = cls.isInstance(new A());

System.out.println(b2);

} catch (Throwable e) {

System.err.println(e);

}

}

}

 
A의 클래스객체가 만들어진다. 그리고 class instance objects 가 A 의 인스턴스인지 체크한다. 
Integer(37) 은 아니지만, new A() 는 True 이다.


클래스 메쏘드 찾기
reflection 의 가장 기초적인 쓰임은 클래스에서 정의한 메소드가 무엇인지 찾아내는 것이다. 이를 위해서 다음 코드가 사용될 수 있다.

import java.lang.reflect.*;


public class method1 {

private int f1(Object p, int x) throws NullPointerException {

if (p == null)

throw new NullPointerException();

return x;

}


public static void main(String args[]) {

try {

Class cls = Class.forName("method1");


Method methlist[] = cls.getDeclaredMethods();

for (int i = 0; i < methlist.length; i++) {

Method m = methlist[i];

System.out.println("name  = " + m.getName());

System.out.println("decl class = " + m.getDeclaringClass());

Class pvec[] = m.getParameterTypes();

for (int j = 0; j < pvec.length; j++)

System.out.println(" param #" + j + " " + pvec[j]);

Class evec[] = m.getExceptionTypes();

for (int j = 0; j < evec.length; j++)

System.out.println("exc #" + j + " " + evec[j]);

System.out.println("return type = " + m.getReturnType());

System.out.println("-----");

}

} catch (Throwable e) {

System.err.println(e);

}

}

}


getDeclaredMethods 을 통해서 메쏘드 리스트를 조회한다. getDeclaredMethods 대신에 getMethods 를 사용하면 상속된 메쏘드에 대한 정보를 얻을 수 있다.

실행결과
   name = f1
   decl class = class method1
   param #0 class java.lang.Object
   param #1 int
   exc #0 class java.lang.NullPointerException
   return type = int
   -----
   name = main
   decl class = class method1
   param #0 class [Ljava.lang.String;
   return type = void
   -----
 
Constructors 에 대한 정보얻기


import java.lang.reflect.*;


public class constructor1 {

public constructor1() {

}


protected constructor1(int i, double d) {

}


public static void main(String args[])

      {

         try {

           Class cls = Class.forName("constructor1");

        

           Constructor ctorlist[] = cls.getDeclaredConstructors();

           for (int i = 0; i < ctorlist.length; i++) {

               Constructor ct = ctorlist[i];

               System.out.println("name = " + ct.getName());

               System.out.println("decl class = " + ct.getDeclaringClass());

               Class pvec[] = ct.getParameterTypes();

               

               for (int j = 0; j < pvec.length; j++)

                  System.out.println("param #" + j + " " + pvec[j]);

               

               Class evec[] = ct.getExceptionTypes();

               

               for (int j = 0; j < evec.length; j++)

                  System.out.println("exc #" + j + " " + evec[j]);

               

               System.out.println("-----");

            }

          }

          catch (Throwable e) {

             System.err.println(e);

          }

      }

}

이 프로그램에서는 리턴타입정보가 없는데... 생성자는 리턴타입을 갖지 않기 때문이다.
실행결과
   name = constructor1
   decl class = class constructor1
   -----
   name = constructor1
   decl class = class constructor1
   param #0 int
   param #1 double
   -----
 
Class Fields 찾기

   import java.lang.reflect.*;


public class field1 {

private double d;

public static final int i = 37;

String s = "testing";


public static void main(String args[])

      {

         try {

        Class cls = Class.forName("field1");

        Field fieldlist[] = cls.getDeclaredFields();

             for (int i = 0; i < fieldlist.length; i++) {

            Field fld = fieldlist[i];

                 System.out.println("name = " + fld.getName());

                 System.out.println("decl class = " + fld.getDeclaringClass());

                 System.out.println("type = " + fld.getType());

                 int mod = fld.getModifiers();

                 System.out.println("modifiers = " + Modifier.toString(mod));

                 System.out.println("-----");

            }

          }

          catch (Throwable e) {

             System.err.println(e);

          }

       }

}

이전예제와 비슷하다. 새로운 특징은 modifier 의 사용이다.
modifier는 'private int' 와 같은 필트멤버를 표현하기위한 reflection class 이다.
modifier 는 숫자로 표현된다. Modifier.toString 은 'final' 앞의 'static' 과 같은 선언순서의 문자열표현을 리턴한다.

실행결과 :
  name = d
   decl class = class field1
   type = double
   modifiers = private
   -----
   name = i
   decl class = class field1
   type = int
   modifiers = public static final
   -----
   name = s
   decl class = class field1
   type = class java.lang.String
   modifiers =
   ----- 
 
이름으로 메쏘드 실행하기.
  import java.lang.reflect.*;


public class method2 {

public int add(int a, int b) {

return a + b;

}


public static void main(String args[]) {

try {

Class cls = Class.forName("method2");

Class partypes[] = new Class[2];

partypes[0] = Integer.TYPE;

partypes[1] = Integer.TYPE;

Method meth = cls.getMethod("add", partypes);

method2 methobj = new method2();

Object arglist[] = new Object[2];

arglist[0] = new Integer(37);

arglist[1] = new Integer(47);

Object retobj = meth.invoke(methobj, arglist);

Integer retval = (Integer) retobj;

System.out.println(retval.intValue());

} catch (Throwable e) {

System.err.println(e);

}

}

}

프로그램이 add 라는 메쏘드를 실행시키는데 실행시까지 알지 못한다고 가정해보자. 메쏘드 이름은 실행시간에 알수있다. 
getMethod 는 클래스에서 두개의 숫자 파라미터와 해당 이름을 가진 메쏘드를 찾아낸다.


새로운 객체 만들기

 import java.lang.reflect.*;


public class constructor2 {

public constructor2() {

}


public constructor2(int a, int b) {

System.out.println("a = " + a + " b = " + b);

}


public static void main(String args[]) {

try {

Class cls = Class.forName("constructor2");

Class partypes[] = new Class[2];

partypes[0] = Integer.TYPE;

partypes[1] = Integer.TYPE;

Constructor ct = cls.getConstructor(partypes);

Object arglist[] = new Object[2];

arglist[0] = new Integer(37);

arglist[1] = new Integer(47);

Object retobj = ct.newInstance(arglist);

} catch (Throwable e) {

System.err.println(e);

}

}

}


필드값 바꾸기

  import java.lang.reflect.*;


public class field2 {

public double d;


public static void main(String args[]) {

try {

Class cls = Class.forName("field2");

Field fld = cls.getField("d");

field2 f2obj = new field2();

System.out.println("d = " + f2obj.d);

fld.setDouble(f2obj, 12.34);

System.out.println("d = " + f2obj.d);

} catch (Throwable e) {

System.err.println(e);

}

}

}

 
배열의 사용

import java.lang.reflect.*;


public class array1 {

public static void main(String args[]) {

try {

Class cls = Class.forName("java.lang.String");

Object arr = Array.newInstance(cls, 10);

Array.set(arr, 5, "this is a test");

String s = (String) Array.get(arr, 5);

System.out.println(s);

} catch (Throwable e) {

System.err.println(e);

}

}

}

 

 import java.lang.reflect.*;


public class array2 {

public static void main(String args[]) {

int dims[] = new int[] { 5, 10, 15 };

Object arr = Array.newInstance(Integer.TYPE, dims);


Object arrobj = Array.get(arr, 3);

Class cls = arrobj.getClass().getComponentType();

System.out.println(cls);

arrobj = Array.get(arrobj, 5);

Array.setInt(arrobj, 10, 37);


int arrcast[][][] = (int[][][]) arr;

System.out.println(arrcast[3][5][10]);

}

}