[Java] Reflection

벼랑 끝 코딩·2025년 3월 17일
0

Java

목록 보기
31/40

Spring을 사용하면 마법같은 일을 아주 간단하게 수행할 수 있다.
그 내부에는 복잡한 Java의 문법이 수행되는데, 알고 이해하면 더 빠르게 학습할 수 있다.
이번엔 Spring Servlet의 핵심 기술인 리플렉션에 대해 알아보자.

Reflection

나는 나를 볼 수 없다. 나를 볼 수 없으니 내가 어떻게 생겼는지도 모른다.
하지만 거울에 반사해서 나를 본다면 나의 형체를 확인할 수 있다.

Reflect는 반사하다라는 의미를 가지고 있다.
Java에서는 객체를 반사시켜 객체의 구조를 들여다볼 수 있다는 의미를 가진다.
객체를 반사할 수 있는 방법은 총 3가지가 있다.

Reflection 방법

class Clazz {
	// 코드
}

public void reflectionMethod() {
	Clazz clazz = new Clazz();
    
    //  ** 클래스로 접근 **
    Class<Clazz> clazzObj1 = Clazz.class;
    
    //  ** 객체로 접근 **
    Clazz<? extends Clazz> clazzObj2 = clazz.getClass();
    
    //  ** 문자열로 조회 **
    String clazzName = "clazzPath.Clazz";
    Clazz<?> clazzObj3 = Class.forName(clazzName);
  1. 클래스이름.class로 클래스를 통해 접근할 수 있다.
  2. 객체의 getClass() 메서드로 객체를 통해 접근할 수 있다.
  3. 클래스 경로와 이름으로 문자열을 통해 접근할 수 있다.

클래스 메타데이터

클래스를 통해서 클래스 이름은 물론이고 패키지, 부모 클래스, 구현 인터페이스 등
다양한 클래스 메타데이터를 얻을 수 있다.
하지만 클래스 메타데이터는 빙산의 일각에 불과하다.
리플렉션을 통해 아주 많은 정보들이 기다리고 있다.

필드 정보

클래스에는 보통 여러 필드들이 선언되어 있다.
리플렉션을 사용하면 필드들의 정보도 살펴볼 수 있다.

필드 획득

class Clazz {
	// 코드
}

public void reflectionMethod() {
	Clazz clazz = new Clazz();
    Clazz<? extends Clazz> clazzObj = clazz.getClass();
    
    Field[] publicFields = clazzObj.getFields();
    Field[] allFields = clazzObj.getDeclaredFields();
}

getFields() 메서드를 사용하면 모든 public 필드를 확인할 수 있다.
getDeclaredFields() 메서드를 사용하면 상속을 제외한 모든 필드를 확인할 수 있다.

필드 변경

조회한 필드의 값을 변경할 수도 있다.
절대 바꿀 수 없을 것만 같았던 private 필드마저 변경할 수 있다.

class Clazz {

	private String name;
    
    public Clazz(String name) {
    	this.name = name;
    }
    
	// 코드
}

public void reflectionMethod() {
	Clazz clazz = new Clazz("beforeName");
    Clazz<? extends Clazz> clazzObj = clazz.getClass();
    
    String fieldName = "name";
    Field privateField = clazzObj.getDeclaredField(fieldName);
    
    privateField.setAccessible(true);  // ** private 필드 접근 허용 **
    privateField.set(clazz, "afterName");  //  ** 필드 값 변경 **
    
}

setAccessible(true) 메서드를 사용하면 private 필드에도 접근이 허용된다.
set() 메서드에 인스턴스와 파라미터를 전달하면 필드의 값을 변경할 수 있다.

메서드 정보

클래스에는 다양한 메서드들이 선언되어 있을 것이다.
리플렉션을 사용하면 메서드의 정보도 살펴볼 수 있다.

메서드 획득

class Clazz {
	// 코드
}

public void reflectionMethod() {
	Clazz clazz = new Clazz();
    Clazz<? extends Clazz> clazzObj = clazz.getClass();
    
    Method[] publicMethods = clazzObj.getMethods();
    Method[] allMethods = clazzObj.getDeclaredMethods();
}

getMethods() 메서드는 모든 public 메서드를 반환한다.
getDeclaredMethods() 메서드는 상속 메서드를 제외모든 메서드를 반환한다.

메서드 호출

class Clazz {
	// 코드
    
    public int clazzMethod() {
    	// 메서드 바디
    }
}

public void clazzMethod() {
	Clazz clazz = new Clazz();
    Clazz<? extends Clazz> clazzObj = clazz.getClass();
    
    //  ** 정적 메서드 호출 **
    clazzObj.clazzMethod();
    
    //  ** 클래스 이름, 파라미터 타입을 통한 동적 메서드 호출 **
    String methodName = "clazzMethod";
    Method publicMethod = clazzObj.getMethod(methodName, int.class);
    Object return = publicMethod.invoke(clazz, 1);
}

심지어 메서드를 호출할 수도 있다.
평소에 메서드를 사용하는 것처럼 정적으로 호출할 수도 있고,
변수와 invoke() 메서드를 통해 동적으로 메서드를 호출할 수도 있다.

생성자 정보

객체를 생성할 수 있는 다양한 생성자 정보를 얻을 수도 있다.

생성자 획득

class Clazz {

	private String name;
    
    public Clazz() {
    	// 코드
    }
    
    private Clazz() {
    	// 코드
    }
}

public void reflectionMethod() {
	Clazz clazz = new Clazz();
    Clazz<? extends Clazz> clazzObj = clazz.getClass();
    
    Constructors<?>[] publicConstructors = classObj.getConstructors();
    Constructors<?>[] allConstructors = classObj.getDeclaredConstructors();
}

getConstructors() 메서드로 모든 public 생성자를 얻을 수 있고,
getDeclaredConstructors() 메서드로 모든 생성자를 얻을 수 있다.

생성자 호출

class Clazz {

	private String name;
    
    public Clazz() {
    	// 코드
    }
    
    private Clazz(String name) {
    	// 코드
    }
}

public void reflectionMethod() {
	Clazz clazz1 = new Clazz();
    Clazz<? extends Clazz> clazzObj = clazz.getClass();
    
    //  ** String 타입을 매개변수로 사용하는 생성자 획득 **
    Constructors<?> privateConstructor = classObj.getDeclaredConstructor(String.class);
    
    privateConstructor.setAccessible(true);  // ** private 생성자 접근 허용 **
    Object clazz2 = privateConstructor.newInstance("name");  // ** 생성자 호출 **
}

생성자가 사용하는 매개변수 타입을 파라미터로 전달하면, 해당 생성자를 조회할 수 있다.
setAccessible(true) 메서드를 통해 private 생성자의 접근을 허용하고,
newInstance() 메서드를 통해 생성자를 호출하여 인스턴스를 생성할 수 있다.

Reflection 활용

언뜻 보아도 Reflection의 힘은 강력하다.
런타임 중에 인스턴스의 다양한 정보를 확인할 수 있고
무엇보다 일반적으로 메서드나 필드를 지정해서 제어하던 것과는 달리,
변수를 통해 동적으로 호출할 수 있다는 부분이 예사롭지 않다.
거기에 private 접근 제어자에도 접근할 수 있다.

이러한 기능을 바탕으로 Reflection은 Spring의 Servlet의 핵심 기술로 사용된다.
Client Request의 start-line의 request-target에서 URL 일부를 발췌하여
동일한 메서드 이름을 호출하여 response를 대응할 수 있다.
커맨드 패턴을 활용하여 명령 하나마다 하나의 클래스를 설계했던 것과는 달리,
Reflection으로 하나의 클래스에 여러 메서드를 통하여 비슷한 URL을 묶어 관리할 수 있다.

마무리

Reflection은 애노테이션과 함께 더욱 강력한 기능을 제공한다.

Servlet을 학습하기 전에 애노테이션과 리플렉션 기술을 함께 학습하여
심도 있게 개념을 이해해보자.

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글