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을 사용해서 동적으로 로딩되는 자바 컴포넌트(클래스)의 속성을 얻을 수 있다.
예제
public class ReflectionController {
public static void main(String args[]) {
try {
Class c = Class.forName("java.lang.String");
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);
}
}
}
실행 결과
byte[] java.lang.String.value()
public boolean java.lang.String.equals(java.lang.Object)
public int java.lang.String.length()
public java.lang.String java.lang.String.toString()
public int java.lang.String.hashCode()
public void java.lang.String.getChars(int,int,char[],int)
public int java.lang.String.compareTo(java.lang.Object)
public int java.lang.String.compareTo(java.lang.String)
public int java.lang.String.indexOf(int,int)
public int java.lang.String.indexOf(java.lang.String)
public int java.lang.String.indexOf(java.lang.String,int)
public int java.lang.String.indexOf(int)
static int java.lang.String.indexOf(byte[],byte,int,java.lang.String,int)
static void java.lang.String.checkIndex(int,int)
public static java.lang.String java.lang.String.valueOf(char[])
public static java.lang.String java.lang.String.valueOf(java.lang.Object)
public static java.lang.String java.lang.String.valueOf(long)
public static java.lang.String java.lang.String.valueOf(char[],int,int)
public static java.lang.String java.lang.String.valueOf(int)
public static java.lang.String java.lang.String.valueOf(char)
public static java.lang.String java.lang.String.valueOf(double)
public static java.lang.String java.lang.String.valueOf(float)
public static java.lang.String java.lang.String.valueOf(boolean)
byte java.lang.String.coder()
private static java.lang.Void java.lang.String.rangeCheck(char[],int,int)
static void java.lang.String.checkBoundsOffCount(int,int,int)
boolean java.lang.String.isLatin1()
public char java.lang.String.charAt(int)
public int java.lang.String.codePointAt(int)
public int java.lang.String.codePointBefore(int)
public int java.lang.String.codePointCount(int,int)
public int java.lang.String.offsetByCodePoints(int,int)
static void java.lang.String.checkBoundsBeginEnd(int,int,int)
public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException
void java.lang.String.getBytes(byte[],int,byte)
public byte[] java.lang.String.getBytes()
public void java.lang.String.getBytes(int,int,byte[],int)
public byte[] java.lang.String.getBytes(java.nio.charset.Charset)
public boolean java.lang.String.contentEquals(java.lang.StringBuffer)
public boolean java.lang.String.contentEquals(java.lang.CharSequence)
private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder)
public boolean java.lang.String.regionMatches(int,java.lang.String,int,int)
public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int)
public boolean java.lang.String.startsWith(java.lang.String,int)
public boolean java.lang.String.startsWith(java.lang.String)
public int java.lang.String.lastIndexOf(java.lang.String)
public int java.lang.String.lastIndexOf(int)
static int java.lang.String.lastIndexOf(byte[],byte,int,java.lang.String,int)
public int java.lang.String.lastIndexOf(int,int)
public int java.lang.String.lastIndexOf(java.lang.String,int)
public java.lang.String java.lang.String.substring(int,int)
public java.lang.String java.lang.String.substring(int)
public boolean java.lang.String.isEmpty()
public java.lang.String java.lang.String.replace(char,char)
public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)
public boolean java.lang.String.matches(java.lang.String)
public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String)
public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String,int)
public java.lang.String[] java.lang.String.split(java.lang.String)
public java.lang.String java.lang.String.toLowerCase()
public java.lang.String java.lang.String.toLowerCase(java.util.Locale)
public java.lang.String java.lang.String.toUpperCase()
public java.lang.String java.lang.String.toUpperCase(java.util.Locale)
public java.lang.String java.lang.String.trim()
public java.lang.String java.lang.String.strip()
public java.lang.String java.lang.String.stripLeading()
public java.lang.String java.lang.String.stripTrailing()
private int java.lang.String.indexOfNonWhitespace()
public java.util.stream.Stream java.lang.String.lines()
public java.lang.String java.lang.String.repeat(int)
private int java.lang.String.lastIndexOfNonWhitespace()
private static int java.lang.String.outdent(java.util.List)
public boolean java.lang.String.isBlank()
public char[] java.lang.String.toCharArray()
public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[])
public java.lang.String java.lang.String.resolveConstantDesc(java.lang.invoke.MethodHandles$Lookup)
public java.lang.Object java.lang.String.resolveConstantDesc(java.lang.invoke.MethodHandles$Lookup) throws java.lang.ReflectiveOperationException
public java.util.stream.IntStream java.lang.String.codePoints()
public boolean java.lang.String.equalsIgnoreCase(java.lang.String)
public int java.lang.String.compareToIgnoreCase(java.lang.String)
public boolean java.lang.String.endsWith(java.lang.String)
public java.lang.CharSequence java.lang.String.subSequence(int,int)
public java.lang.String java.lang.String.concat(java.lang.String)
public boolean java.lang.String.contains(java.lang.CharSequence)
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable)
public java.lang.String java.lang.String.indent(int)
public java.lang.String java.lang.String.stripIndent()
public java.lang.String java.lang.String.translateEscapes()
public java.util.stream.IntStream java.lang.String.chars()
public java.lang.Object java.lang.String.transform(java.util.function.Function)
public java.lang.String java.lang.String.formatted(java.lang.Object[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public static java.lang.String java.lang.String.copyValueOf(char[])
public native java.lang.String java.lang.String.intern()
static void java.lang.String.checkOffset(int,int)
static java.lang.String java.lang.String.valueOfCodePoint(int)
public java.util.Optional java.lang.String.describeConstable()
private static java.lang.String java.lang.String.lambda$stripIndent$3(int,java.lang.String)
private static java.lang.String java.lang.String.lambda$indent$2(int,java.lang.String)
private static java.lang.String java.lang.String.lambda$indent$1(java.lang.String)
private static java.lang.String java.lang.String.lambda$indent$0(java.lang.String,java.lang.String)
이 프로그램은 java.lang.String으 ㅣ속성과 반환값에 따라 메소드 리스트를 출력한다.
이 프로그램은 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 ReflectionController {
private int f1(Object p, int x) {
if(p == null) throw new NullPointerException();
return x;
}
public static void main(String args[]) {
try {
Class c = ReflectionController.class;
Method methList[] = c.getDeclaredMethods();
for(Method m : methList) {
System.out.println("name = " +m.getName());
System.out.println("decl class = " +m.getDeclaringClass());
Class pvec[] = m.getParameterTypes();
for(int i=0; i<pvec.length; i++)
System.out.println("파라미터 : "+pvec[i]);
Class evec[] = m.getExceptionTypes();
for(int j=0; j<evec.length; j++) {
System.out.println("예외 : "+evec[j]);
}
System.out.println("return type = "+ m.getReturnType());
System.out.println("----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
getDeclaredMethods 을 통해서 메쏘드 리스트를 조회한다. getDeclaredMethods 대신에 getMethods 를 사용하면 상속된 메쏘드에 대한 정보를 얻을 수 있다.
실행결과
name = main
decl class = class com.***.study.controller.ReflectionController
파라미터 : class [Ljava.lang.String;
return type = void
----
name = f1
decl class = class com.***.study.controller.ReflectionController
파라미터 : class java.lang.Object
파라미터 : int
return type = int
----
Constructors에 대한 정보 얻기
import java.lang.reflect.*;
public class ReflectionController {
public ReflectionController() {
}
protected ReflectionController(int i, double d) {
}
public static void main(String args[]) {
Class cls = ReflectionController.class;
Constructor ctl[] = cls.getDeclaredConstructors();
for (Constructor ct : ctl) {
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("-----");
}
}
}
이 프로그램에서는 리턴타입정보가 없는데... 생성자는 리턴타입을 갖지 않기 때문이다.
실행결과
name ; com.***.study.controller.ReflectionController
decl class = class com.***.study.controller.ReflectionController
-----
name ; com.***.study.controller.ReflectionController
decl class = class com.***.study.controller.ReflectionController
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]);
}
}
출처: https://gyrfalcon.tistory.com/entry/Java-Reflection [Minsub's Blog]
'Study > Java' 카테고리의 다른 글
[Java] 주민등록번호 유효성 검사 (0) | 2021.12.14 |
---|---|
[Java] StringUtils.isBlank 와 StringUtils.isEmpty 차이 (0) | 2021.12.13 |
[Java] JAR WAR 차이점 및 특징 (0) | 2021.11.05 |
[Java] 날짜 / 시간 차이 계산 (0) | 2021.03.22 |
[Java] split 점(.)으로 하기 (0) | 2021.02.04 |
댓글