[Java] Reflection

sundays·2023년 3월 28일
0

java

목록 보기
1/1

이전에 Dynamic Proxy에 대한 포스트를 했었는데요. 이때 사용된 java.reflect 에서도 Reflection API를 사용해서 인스턴스를 생성해주고 있었습니다. 그리고 최근에 포스팅한 Serializable에서도 Reflection을 사용하고 있어서 Object 생성시 GC의 발생 때문에 시스템 부하가 늘어날수 있다고 언급한적이 있습니다.

Reflection은 컴파일된 Java 코드에서 역으로 클래스를 불러서 메서드 및 변수를 구해오는 방법 입니다. Java에서 기본적으로 제공하는 API로 사용방법을 알면 라이브러리 추가 없이 사용할 수 있습니다. 특히 클래스를 동적 로딩 하여 사용할 때 많이 사용되며 de-compile할때도 자주 사용되는 기법입니다.

Reflection

Reflection을 사용하기 위한 예제를 작성하겠습니다. 저는 access modifier 에 따라 다른 I/O 를 설정하기 위해 작성하기 위해 access modifier를 다양하게 설계하였습니다.

Product.java

package reflection.data;

public class Product {
	private String category = "cosmetic";
	public String brand = "RMK";
	
	public Product() { }
	private void method1() { System.out.println("method1"); }
	public void method2() { System.out.println("method2"); }
	private void method3() { System.out.println("method3"); }
}

Cosmetic.java

package reflection.data;

public class Cosmetic extends Product{
	public String name = "eye-shadow";
	private String color = "brown";
    public static int FACTORY = 2;
	
    public static int getFactory(int number) {
    	System.out.println("number : " + number);
        return number;
    }
	public Cosmetic() {}
	private Cosmetic(String s) {
		this.name = s;
	}
	public int amount(int n) { System.out.println("amount : " + n); return n;}
	private int method4(int n) { System.out.println("method4"); return n;}
}

class declare

먼저 Refection을 사용하려는 class에서는 import로 reflection을 import 해주어야 합니다
먼저 클래스를 가져오는 부분 부터 작성해보겠습니다.

1. CLASS 가져오기 (정석)

import java.lang.reflect.*;

import reflection.data.Cosmetic;

public Main {
	public static void main(String[] args) {
    	Class c = Cosmetic.class;
        System.out.println("className : " + c.getName());
    }
}

[결과]

className : reflection.data.Cosmetic

2. Class.forName(String)
동적바인딩으로 class를 가져오는 방법이 있는데, Class.forName() 함수를 가지고 Class를 가져와서 출력할 수도 있습니다. 이때는 위에서 사용되었던 import reflection.data.Cosmetic 을 선언해줄 필요가 없습니다.

Class c = Class.forName("reflection.data.Cosmetic");
System.out.println("className : " + c.getName());

[결과]

className : reflection.data.Cosmetic

Constructor

위에서 선언한 클래스를 가지고 contstructor를 가져오겠습니다.

class.getDeclaredConstructor()

getDeclaredConstructor() 는 선언된 Contstructor 중 public 으로 선언되어진 기본 생성자를 가져오게 설계되었습니다

Class c = Class.forName("reflection.data.Cosmetic");
Constructor constructor = c.getDeclaredConstructor();
System.out.println("constructor : " + constructor);

[결과]

constructor : reflection.data.Cosmetic()

getDeclaredContructor(String.class) 매개변수가 String.class 인 매개변수 한개를 받는 Constructor를 찾게 됩니다.

class.getDeclaredConstructors()

모든 접근제어자로 작성된 contructor를 가져옵니다

Class c = Class.forName("reflection.data.Cosmetic");
Constructor[] constructor = c.getDeclaredConstructors();
for (Constructor con: constructor) {
	System.out.println("constructor : " + con);
}

[결과]

constructor : private reflection.data.Cosmetic(java.lang.String)
constructor : public reflection.data.Cosmetic()

class.getConstructors()

오직 public 접근 제어자를 갖고있는 constructor만 리턴합니다.

Class c = Class.forName("reflection.data.Cosmetic");
Constructor[] constructor = c.getConstructors();
for (Constructor con: constructor) {
	System.out.println("constructor : " + con);
}

[결과]

constructor : public reflection.data.Cosmetic()

Method

다음으로는 Class에 접근하여 메서드를 수정하거나 내용을 출력하게 할 수도 있습니다.

class.getDeclaredMethod()

getDeclaredMethod() 는 public 접근 제어자를 가진 method를 반환합니다.

Class c = Class.forName("reflection.data.Cosmetic");
Method method = c.getDeclaredMethod("amount", int.class);
System.out.println("amount : " + method);

[결과]

amount : public reflection.data.Cosmetic.amount(int)

class.getDeclaredMethods()

모든 접근제어자를 가진 method들을 가져옵니다.

Class c = Class.forName("reflection.data.Cosmetic");
Method[] methods = c.getDeclaredMethods();
for (Method m : methods) {
	System.out.println("method : " + m);
}

[결과]

method : public reflection.data.Cosmetic.amount(int)
method : private reflection.data.Cosmetic.method4(int)
method : public reflection.data.Cosmetic.method2()

class.getMethods()

super#현재메서드 를 포함한 public method 전부를 리턴합니다

Class c = Class.forName("reflection.data.Cosmetic");
Method[] methods = c.getMethods();
for (Method m : methods) {
	System.out.println("method : " + m);
}

[결과]

method : public static int reflection.data.Cosmetic.getFactory(int)
method : public int reflection.data.Cosmetic.amount(int)
method : public void reflection.data.Product.method2()
method : public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method : public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method : public final void java.lang.Object.wait() throws java.lang.InterruptedException
method : public boolean java.lang.Object.equals(java.lang.Object)
method : public java.lang.String java.lang.Object.toString()
method : public native int java.lang.Object.hashCode()
method : public final native java.lang.Class java.lang.Object.getClass()
method : public final native void java.lang.Object.notify()
method : public final native void java.lang.Object.notifyAll()

Method.invoke()

Method.invoke() 를 사용하면 Cosmetic 생성자가 가진 메서드를 사용하게 될 수 있습니다.

Cosmetic cosmetic = new Cosmetic();
Class c = Class.forName("reflection.data.Cosmetic");
Method method = c.getDeclaredMethod("amount", int.class);
int result = (int) method.invoke(cosmetic, 10);
System.out.println("Cosmetic.amount : "+ result);

[결과]

Cosmetic.amount : 10

Method.set(null, Object)

만약 constructor 가 아닌 null 에 접근을 하려고 한다면 이 설정은 static method에 접근하여 수정하기 때문에 contructor가 필요가 없다

Field f = c.getDeclaredMethod("getFactory", int.class);
f.set(null, 1);
System.out.println("field.static : " + f.get(null));

Method.setAccessible(boolean)

Method.setAccessible(boolean) 을 사용하면 메서드의 접근 제어자를 변경하게 할 수도 있습니다

Cosmetic cosmetic = new Cosmetic();
Class c = Class.forName("reflection.data.Cosmetic");
Method method = c.getDeclaredMethod("method4", int.class);
method.setAccessible(true);
System.out.println("method : " + method);

[결과]

method : method4

Field

다음으로는 field에 접근하여 field 에 접근하여 메서드의 접근 제어자를 수정하거나 출력을 하게 할 수 있습니다.

class.getDeclaredField(String)

접근 제어자가 public인 매개변수 String field를 리턴합니다

Class c = Class.forName("reflection.data.Cosmetic");
Field f = c.getDeclaredField("name");
System.out.println("field : " + f);

[결과]

field : public reflection.data.Cosemtic.name

class.getDeclaredFields()

모든 필드를 리턴합니다.

Class c = Class.forName("reflection.data.Cosmetic");
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
	System.out.println("field : " + field); 
}

[결과]

field : public reflection.data.Cosmetic.name
field : private reflection.data.Cosmetic.color
field : public static refelection.data.Cosmetic.FACTORY

class.getFields()

class 및 super#class의 public field를 리턴합니다

Class c = Class.forName("reflection.data.Cosmetic");
Field[] fields = c.getFields();
for (Field f : fields) {
	System.out.println("field : " + field); 
}

[결과]

field : public java.lang.String reflection.data.Cosmetic.name
field : public java.lang.String reflection.data.Product.brand
field : public static int reflection.data.Product.FACTORY

class.getField(String)

String 매개변수에 해당하는 필드의 value 를 리턴합니다

Cosmetic cosmetic = new Cosmetic();
Class c = Class.forName("reflection.data.Cosmetic");
Field f = c.getField("name");
System.out.println("field.name : " + f.get(cosmetic));

[결과]

field.name : eye-shadow

field.set(constructor, String)

Constructor 에 field를 가져온 곳에 데이터를 변경하여 리턴한다

Cosmetic cosmetic = new Cosmetic();
Field f = c.getField("name");
f.set(cosmetic, "lip-stick");
System.out.println("field.name : " + f.get(cosmetic));

[결과]

field.name : lip-stick

field.set(null, Object)

만약 constructor 가 아닌 null 에 접근을 하려고 한다면 이 설정은 static field에 접근하여 수정하기 때문에 contructor가 필요가 없다

Field f = c.getField("factory");
f.set(null, 1);
System.out.println("field.static : " + f.get(null));

field.setAccessible(boolean)

field에 접근 제어자를 변경하여 데이터를 변경할 수 있다

Cosmetic cosmetic = new Cosmetic();
Field f = c.getField("color");
f.setAccessible(true);
f.set(cosemtic, "orange");
System.out.println("field.color : " + f.get(cosmetic));

[결과]

field.color : orange

결론

사실 전부 다 머리속에서 markdown 으로 직접 작성하게 되어서 오타가 발생하였을 수도 있습니다.
github 에 내용을 추가하였습니다. reflection은 시스템 비용이 상당하므로 신중히 사용해야 할 내용입니다. 생각보다 de-compile할 기회가 적지 않아 보여서 직접 decompile하여 내용을 정리하면 더 좋을 것 같습니다.

Reference

profile
develop life

0개의 댓글