본문 바로가기

Java Reflection API/Reflection API Intro

Java Reflection API introduction

Java Reflection API의 사용

개요

Java Reflection API는 보통 JVM 상에서 실행되는 자바 프로그램의 내부 구성을 확인하거나 런타임시에 프로그램의 작동방법을 변경하고자 하는 경우에 사용된다.

이 기술을 사용하는 것은 다른 API를 사용하는 것에 비해서 다소 수준이 높은 주제이므로 자바 언어에 대한 확실한 기반 지식이 없이는 별로 도움이 되지 않을 것이다.

일반 응용 프로그래밍에서는 별로 사용할 필요가 없는 편이지만, 특정한 부류의 프로그램을 작성할 때는 Java Reflection API가 필수적으로 요구되는 경우도 있을 수 있다.


Java Reflection API를 사용하므로써 구현 가능한 특징들을 몇가지 알아본다.

확장가능한 오브젝트를 생성한다.

개발자가 개발을 완료한 후에 사용자에 의해서 외부로부터 새로이 추가되는 자바 클래스의 이름을 이용하여 그 클래스의 객체를 생성하고 그 객체의 기능을 사용할 수 있다.

Class Browser, Visual Development Environments

널리 사용되는 Eclipse와 같은 자바 개발환경에서는 개발자가 작성하는 클래스의 이름, 메소드 이름, 속성등을 확인하여 개발환경에 제대로 표시해 줄 필요가 있다. 개발자가 작성한 클래스의 멤버에 대한 정보를 소프트웨어적으로 알아낼 수가 없다면 이런 유형의 프로그램을 작성하기 곤란할 것이다.

Debuggers, Test Tools

디버깅을 편리하게 해 주는 개발환경을 작성하기 위해서는 자바 클래스 내의 private 멤버에 대한 정보도 필수적으로 요구된다.


Reflection API를 사용하는 경우의 문제점

Reflection API가 파워풀한 특징을 가지고 있기는 하지만 꼭 필요한 곳에만 사용할 필요가 있다. 무분별한 사용은 성능이나 기능상 도움이 되지 않는다. 그러므로 Reflection API를 사용하지 않는 방법이 최선이며, 다른 방법이 존재함에도 불구하고 Reflection API를 사용한다면 개발자의 이해가 부족한 경우일 것이다.

성능의 저하

Java Reflection API는 컴파일 타임이 아닌 실행시에 클래스의 정보를 파악해야 하기 때문에 다른 코드보다 느려지는 단점을 가지고 있다. 그러므로 반복적으로 호출되는 로직 부분이나 루프를 구성하는 코드를 Reflection 으로 작성한다면 성능이 현저히 떨어지게 된다.

보안상 문제

Java Reflection API는 자바 애플릿과 같은 보안 모델 안에서 사용되는 경우도 있기 때문에 이러한 환경에서 실행되도록 작성된 프로그램은 Security Manager 가 실행을 허락하지 않는다면 문제가 있을 수 있다.

클래스 내부의 노출

클래스의 내부에는 객체지향 개념에 따라 은닉성을 적용한 멤버 변수나 메소드가 있을 수 있는데, Reflection API를 사용한다면 객체지향 개념에 따라 은닉된 멤버에도 접근할 수 있게 되므로 예기치 못한 부작용이 있을 수 있다. 기능이 오작동하거나 이식성이 떨어지거나 플랫폼을 업그레이드할 때의 영향에 따라 프로그램의 기능이 달라지는 추상화 기능의 오작동이 있을 수 있다.


Class 라는 이름의 자바 클래스

Reflection API의 시작은 Class라는 클래스의 인스턴스를 얻는 것이다. 이 인스턴스를 이용하면 다른 객체의 멤버에 대한 정보를 얻을 수 있고 다른 클래스의 객체도 생성할 수가 있다. Class의 인스턴스를 생성하는 것은 Reflection API 프로그래밍의 시작을 의미한다고 볼 수 있다.

Class Object 얻어내기

특정 클래스의 객체가 이미 존재한다면 그 객체를 이용하여 Class라는 클래스의 인스턴스를 추출할 수 있다.

String str = "Hello";

Class cls = str.getClass();

객체는 없지만 클래스나 자료형이 존재한다면 클래스 이름이나 자료형을 이용하여 Class 의 인스턴스를 얻을 수 있다. 이는 Class의 인스턴스를 얻는 가장 쉬운 방법이다.

Class cls = boolean.class; // 기본 자료형을 이용하는 경우

Class cls = String.class; // 클래스의 이름을 이용하는 경우

Class c = int[][][].class; // 배열형을 이용하는 경우


클래스가 위치한 완전한 패키지 경로를 알고 있다면 그 경로를 문자열로 표현하여 Class.forName()의 아규먼트로 사용할 수도 있다.

Class c = Class.forName("com.duke.MyLocaleServiceProvider");

Class cDoubleArray = Class.forName("[D");

Class cStringArray = Class.forName("[[Ljava.lang.String;");


기본형 Wrapper 클래스의 이름에 TYPE 이라는 필드를 이용하면 기본형에 대한 Class의 인스턴스를 구할 수가 있다.

Class c = Double.TYPE;
Class c = Void.TYPE;


메소드를 이용하여 Class 인스턴스를 구할 수 있는 경우


Class c = javax.swing.JButton.class.getSuperclass(); // 수퍼 클래스의 Class 인스턴스

Class<?>[] c = Character.class.getClasses(); // 모든 public 멤버 클래스, 인터페이스, enums, 부모 클래스의 Class 인스턴스 배열 구함

Class<?>[] c = Character.class.getDeclaredClasses(); // 모든 멤버 클래스, 인터페이스, enums, 부모 클래스의 Class 인스

클래스의 모디파이어와 타입을 확인하는 Reflection 예

import java.lang.annotation.Annotation;

import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import static java.lang.System.out; public class ClassDeclarationSpy { public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); out.format("Class:%n %s%n%n", c.getCanonicalName()); out.format("Modifiers:%n %s%n%n", Modifier.toString(c.getModifiers())); out.format("Type Parameters:%n"); TypeVariable[] tv = c.getTypeParameters(); if (tv.length != 0) { out.format(" "); for (TypeVariable t : tv) out.format("%s ", t.getName()); out.format("%n%n"); } else { out.format(" -- No Type Parameters --%n%n"); } out.format("Implemented Interfaces:%n"); Type[] intfs = c.getGenericInterfaces(); if (intfs.length != 0) { for (Type intf : intfs) out.format(" %s%n", intf.toString()); out.format("%n"); } else { out.format(" -- No Implemented Interfaces --%n%n"); } out.format("Inheritance Path:%n"); List<Class> l = new ArrayList<Class>(); printAncestor(c, l); if (l.size() != 0) { for (Class<?> cl : l) out.format(" %s%n", cl.getCanonicalName()); out.format("%n"); } else { out.format(" -- No Super Classes --%n%n"); } out.format("Annotations:%n"); Annotation[] ann = c.getAnnotations(); if (ann.length != 0) { for (Annotation a : ann) out.format(" %s%n", a.toString()); out.format("%n"); } else { out.format(" -- No Annotations --%n%n"); } // production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static void printAncestor(Class<?> c, List<Class> l) { Class<?> ancestor = c.getSuperclass(); if (ancestor != null) { l.add(ancestor); printAncestor(ancestor, l); } } }

클래스 안에 선언된 멤버를 찾아서 출력하는 Reflection 예

import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Member; import static java.lang.System.out; enum ClassMember { CONSTRUCTOR, FIELD, METHOD, CLASS, ALL } public class ClassSpy { public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); out.format("Class:%n %s%n%n", c.getCanonicalName()); Package p = c.getPackage(); out.format("Package:%n %s%n%n", (p != null ? p.getName() : "-- No Package --")); for (int i = 1; i < args.length; i++) { switch (ClassMember.valueOf(args[i])) { case CONSTRUCTOR: printMembers(c.getConstructors(), "Constructor"); break; case FIELD: printMembers(c.getFields(), "Fields"); break; case METHOD: printMembers(c.getMethods(), "Methods"); break; case CLASS: printClasses(c); break; case ALL: printMembers(c.getConstructors(), "Constuctors"); printMembers(c.getFields(), "Fields"); printMembers(c.getMethods(), "Methods"); printClasses(c); break; default: assert false; } } // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static void printMembers(Member[] mbrs, String s) { out.format("%s:%n", s); for (Member mbr : mbrs) { if (mbr instanceof Field) out.format(" %s%n", ((Field)mbr).toGenericString()); else if (mbr instanceof Constructor) out.format(" %s%n", ((Constructor)mbr).toGenericString()); else if (mbr instanceof Method) out.format(" %s%n", ((Method)mbr).toGenericString()); } if (mbrs.length == 0) out.format(" -- No %s --%n", s); out.format("%n"); } private static void printClasses(Class<?> c) { out.format("Classes:%n"); Class<?>[] clss = c.getClasses(); for (Class<?> cls : clss) out.format(" %s%n", cls.getCanonicalName()); if (clss.length == 0) out.format(" -- No member interfaces, classes, or enums --%n"); out.format("%n"); } }