Reflection API

정미·2022년 7월 30일
0

Computer Science

목록 보기
56/81

정의

  • 사전적 의미: 투영, 반사
  • 객체를 통해 클래스의 정보를 분석하는 프로그램 기법
  • 구체적인 클래스 타입을 알지 못해도 그 클래스의 정보(메서드, 타입, 변수 등)에 접근할 수 있게 해주는 자바 API

코드 설명

public class Calculator {
    public int result = 0;

    public void calculate(int a, int b) {
        result = a + b;
    }
}
public class AddCalculator extends Calculator {
    public void print(int a, int b) {
        calculate(a, b);
        System.out.println("result = " + result);
    }

    public int getResult() {
        return result;
    }
}
public static void main(String[] args) {
    Calculator calculator = new AddCalculator();
    // calculator.print();  -> 컴파일 에러
}

자바는 컴파일러를 사용하고, 컴파일 타임에 변수의 타입이 결정된다. calculator는 컴파일 타임에 Calculator로 타입이 결정되었기 때문에 AddCalculator의 필드와 메서드는 사용하지 못한다.

Reflection API를 사용하면 Calculator 타입의 calculatorAddCalculator의 메서드를 호출할 수 있게 해준다.

public static void main(String[] args) {
    Calculator calculator = new AddCalculator();
    Class addCalculatorClass = AddCalculator.class;

    Method print = addCalculatorClass.getMethod("print");
    Object[] params = new Object[2];
    params[0] = new Integer(1);
    params[1] = new Integer(2);
    // 메서드명.invoke(메서드를 실행시킬 객체, 메서드에 넘길 인자)
    print.invoke(calculator, params);  // result = 3

    Method getResult = addCalculatorClass.getMethod("getResult");
    int result = (int) getResult.invoke(calculator, null);
    System.out.println(result);  // 3
}

calculator가 구체적인 클래스 타입인 AddCalculator를 알지 못해도 메서드에 접근하였다.

특징

  • 실행 중인 자바 프로그램 내부를 검사하고 속성을 수정할 수 있도록 한다.
  • 인스턴스의 구체 클래스 타입을 알지 못해도 클래스의 이름만으로 해당 클래스의 정보를 대부분 가져올 수 있다.
  • 자바 클래스가 가진 생성자, 필드, 메서드를 얻거나 보여줄 수 있다.
  • 자바가 아닌 다른 언어에서는 없는 특징

동작 방식

  1. JVM이 실행되면 자바 코드가 컴파일러를 거쳐 바이트 코드로 변환된 후 static 영역에 로드된다.
  2. Reflection API는 클래스 이름을 안다면 static 영역을 보고 정보를 가져온다.

활용

어플리케이션 개발보다 프레임워크, 라이브러리에 사용된다.

사용자가 어떤 클래스를 만들지 예측할 수 없기 때문에 동적으로 해결해주기 위해 Reflection API 사용

  1. Spring BeanFacotry
    • 어플리케이션이 실행한 후 객체가 호출될 때 reflection 기술을 사용해 동적으로 객체의 인스턴스를 생성한다.
  2. Spring Data JPA - Entity의 기본 생성자
    • 어플리케이션 실행 중에 동적으로 객체 생성 시 사용
    • Reflection API는 생성자의 인자 정보를 가져올 수 없다.
      • 기본 생성자 없이 파라미터가 있는 생성자만 존재한다면 java reflection이 객체를 생성할 수 없다.
      • Hibernate 같은 구현체들은 유연하게 바이트코드를 조작하는 라이브러리를 통해서 이 문제를 회피하지만 될 때도 있고 안 될 때도 있다. 완벽한 해결책은 아니므로 JPA 스펙 가이드대로 기본 생성자를 반드시 넣어주어야 한다. _ 김영한
    • 반드시 기본 생성자가 있어야 객체를 생성할 수 있고, Reflection API로 필드 값을 넣어준다.
  3. BuilderTool
    • 빌더 툴을 사용해서 소프트웨어 컴포넌트를 만든다.
    • reflection을 사용해서 동적으로 로딩되는 자바 컴포넌트(클래스)의 속성을 얻을 수 있다.
  4. intellij 자동완성
  5. jackson 라이브러리
  6. Hibernate
  7. 등..

단점

우리가 코드를 작성하면서 Reflection을 활용할 일은 거의 없다. 사용하지 않을 수 있다면 사용하지 않는 것이 좋다.

  1. 성능 오버헤드
    • JVM 최적화 불가능
    • 컴파일 타임이 아닌 런타임에 동적으로 타입을 분석하고 가져오기 때문
  2. 추상화가 깨진다.
    • 접근할 수 없는 private 인스턴스 변수와 메서드에 접근할 수 있다.

사용법

예시

  • 명령어의 인자값으로 받은 클래스의 메서드 리스트 출력하기
    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)

0. Reflection을 이용한 Set up

  1. 수정을 원하는 클래스의 java.lang.Class 객체 얻기
    • java.lang.Class: 클래스 표현, 실행 중인 자바 프로그램과 인터페이스한다.
    • 클래스 정보 얻는 방법
      1. Class c = Class.forName("java.lang.String");
      2. Class c = int.class;
      3. Class c = Integer.TYPE;
  2. 클래스에 정의된 모든 메서드 리스트 얻기
    • getDeclaredMethods() 등 호출
  3. 정보 수정을 위해 Reflection API 사용

1. Class의 instance인지 확인

Class.isInstance()

class A {}

public class InstanceOperator {
	public static void main(String args[]) {
		try {
			Class cls = Class.forName("A");
			
			boolean b1 = cls.isInstance(new Integer(37));  // false
			System.out.println(b1);
			
			boolean b2 = cls.isInstance(new A());  // true
			System.out.println(b2);
			
		} catch (Throwable e) {
			System.err.println(e);
		}
	}
}

2. method 정보

클래스에서 정의한 메서드가 무엇인지 찾아내기 → reflection의 가장 기초 쓰임


import java.lang.reflect.*;

public class CheckMethod {
	private int check(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("CheckMethod");
			Method[] methods = cls.getDeclaredMethods();
			for (int i = 0; i < methods .length; i++) {
				Method method = methods [i];
				System.out.println("method name  = " + method.getName());
				System.out.println("declaring class = " + method.getDeclaringClass());
				Class[] parameterTypes = method.getParameterTypes();
				
				for (int j = 0; j < parameterTypes.length; j++)
					System.out.println(" param #" + j + " " + parameterTypes[j]);
				
				Class[] exceptions = method.getExceptionTypes();
				
				for (int j = 0; j < exceptions.length; j++)
					System.out.println("exception #" + j + " " + exceptions[j]);
				
				System.out.println("return type = " + method.getReturnType());
				System.out.println("-----");
			}
		} catch (Throwable e) {
			System.err.println(e);
		}
	}
}
<실행 결과>

method name = check
decl class = class CheckMethod
param #0 class java.lang.Object   
param #1 int   
exception #0 class java.lang.NullPointerException   
return type = int   
-----   
name = main   
decl class = class CheckMethod
param #0 class [Ljava.lang.String;   
return type = void   
-----
  • getDeclaredMethods(): 메서드 리스트 조회
  • getMethods(): 상속된 메서드 정보 조회

3. Constructors 정보

import java.lang.reflect.*;

public class ContrutorReflection {
	public ContrutorReflection() {
	}

	protected ContrutorReflection(int i, double d) {
	}

	public static void main(String args[]) {
         try {
           Class cls = Class.forName("ContrutorReflection");
        
           Constructor[] constructors = cls.getDeclaredConstructors();
           for (int i = 0; i < constructors.length; i++) {
               Constructor constructor = constructors[i];
               System.out.println("constructor name = " + constructor.getName());
               System.out.println("declaring class = " + constructor.getDeclaringClass());
               Class[] params = constructor.getParameterTypes();
               
               for (int j = 0; j < params.length; j++)
                  System.out.println("param #" + j + " " + params[j]);
               
               Class[] exceptions = constructor.getExceptionTypes();
               
               for (int j = 0; j < exceptions.length; j++)
                  System.out.println("exception #" + j + " " + exceptions[j]);
               
               System.out.println("-----");
            }
          } catch (Throwable e) {
             System.err.println(e);
          }
      }
}
<실행 결과>

contructor name = ContrutorReflection
declaring class = class ContrutorReflection
-----   
contructor name = ContrutorReflection
declaring class = class ContrutorReflection
param #0 int   
param #1 double   
-----
  • 생성자는 리턴 타입을 가지지 않는다.

4. Class Fields 정보

  • Modifier
    • private int 필드를 표현하기 위한 reflection class
    • 숫자로 표현된다.
    • Modifier.toString() : static과 final과 같은 키워드의 선언 순서의 문자열 표현 리턴
import java.lang.reflect.*;

public class FieldReflection {
	public static final int i = 37;
	private doubld d;
	String str = "testing";

	public static void main(String args[]) {
         try {
           Class cls = Class.forName("FieldReflection");
        
           Field[] fields = cls.getDeclaredFields();
           for (int i = 0; i < fields .length; i++) {
               Field field = fields[i];
               System.out.println("field name = " + field.getName());
               System.out.println("declaring class = " + field.getDeclaringClass());
               System.out.println("type = " + field.getType());

               int modifier = field.getModifiers();
               System.out.println("modifiers" + Modifier.toString(modifier));
               System.out.println("-----");
            }
          } catch (Throwable e) {
             System.err.println(e);
          }
      }
}
<실행 결과>

name = d   
decl class = class FieldReflection
type = double   
modifiers = private   
-----   
name = i   
declaring class = class FieldReflection
type = int   
modifiers = public static final   
-----   
name = str   
declaring class = class FieldReflection
type = class java.lang.String   
modifiers =   
----- 

5. 클래스명으로 메서드 실행하기

import java.lang.reflect.*;

public class MethodReflection {
	public int add(int a, int b) {
		return a + b;
	}

	public static void main(String args[]) {
		try {
			Class cls = Class.forName("MethodReflection");
			Class[] paramtypes = new Class[2];
			paramtypes[0] = Integer.TYPE;
			paramtypes[1] = Integer.TYPE;
			Method method = cls.getMethod("add", paramtypes);

			MethodReflection methodReflection = new MethodReflection();
			Object[] params = new Object[2];
			params[0] = new Integer(37);
			params[1] = new Integer(47);
			Object result = method.invoke(methodReflection, params);
			Integer resultValue = (Integer) result;
			System.out.println(resultValue.intValue());
		} catch (Throwable e) {
			System.err.println(e);
		}
	}
}
  • 프로그램이 add()를 실행시까지 알지 못한다면, 메서드 이름은 실행 시간에 알 수 있다.
  • getMethod() : 클래스에서 숫자 파라미터와 해당 이름을 가진 메서드를 찾아낸다.

6. 새로운 객체 생성

import java.lang.reflect.*;

public class ContructorReflection {
	public ConstructorReflection() {
	}

	public ConstructorReflection(int a, int b) {
		System.out.println("a = " + a + " b = " + b);
	}

	public int add(int a, int b) {
		return a + b;
	}

	public static void main(String args[]) {
		try {
			Class cls = Class.forName("ConstructorReflection");
			Class[] paramtypes = new Class[2];
			paramtypes[0] = Integer.TYPE;
			paramtypes[1] = Integer.TYPE;
			Contructor constructor = cls.getContructor(paramtypes);

			Object[] params = new Object[2];
			params[0] = new Integer(37);
			params[1] = new Integer(47);
			Object result = contructor.newInstance(params);
		} catch (Throwable e) {
			System.err.println(e);
		}
	}
}

7. 필드값 변경

import java.lang.reflect.*;

public class FieldReflection {
	public double d;

	public static void main(String args[]) {
		try {
			Class cls = Class.forName("FieldReflection");
			Field field = cls.getField("d");
			FieldReflection fieldReflection = new FieldReflection();
			System.out.println("d = " + fieldRelection.d);
			field.setDouble(fieldReflection, 12.34);
			System.out.println("d = " + fieldReflection.d);
		} catch (Throwable e) {
			System.err.println(e);
		}
	}
}

8. 배열 사용

import java.lang.reflect.*;

public class ArrayReflection {
	public static void main(String args[]) {
		try {
			Class cls = Class.forName("java.lang.String");
			Object array = Array.newInstance(class, 10);
			Array.set(array, 5, "array test");
			String str = (Stirng) Array.get(array, 5);
			System.out.println(str);
		} catch (Throwable e) {
			System.err.println(e);
		}
	}
}

출처

0개의 댓글