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

나는 나를 볼 수 없다. 나를 볼 수 없으니 내가 어떻게 생겼는지도 모른다.
하지만 거울에 반사해서 나를 본다면 나의 형체를 확인할 수 있다.
Reflect는 반사하다라는 의미를 가지고 있다.
Java에서는 객체를 반사시켜 객체의 구조를 들여다볼 수 있다는 의미를 가진다.
객체를 반사할 수 있는 방법은 총 3가지가 있다.
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);

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

클래스에는 보통 여러 필드들이 선언되어 있다.
리플렉션을 사용하면 필드들의 정보도 살펴볼 수 있다.
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의 힘은 강력하다.
런타임 중에 인스턴스의 다양한 정보를 확인할 수 있고
무엇보다 일반적으로 메서드나 필드를 지정해서 제어하던 것과는 달리,
변수를 통해 동적으로 호출할 수 있다는 부분이 예사롭지 않다.
거기에 private 접근 제어자에도 접근할 수 있다.
이러한 기능을 바탕으로 Reflection은 Spring의 Servlet의 핵심 기술로 사용된다.
Client Request의 start-line의 request-target에서 URL 일부를 발췌하여
동일한 메서드 이름을 호출하여 response를 대응할 수 있다.
커맨드 패턴을 활용하여 명령 하나마다 하나의 클래스를 설계했던 것과는 달리,
Reflection으로 하나의 클래스에 여러 메서드를 통하여 비슷한 URL을 묶어 관리할 수 있다.

Reflection은 애노테이션과 함께 더욱 강력한 기능을 제공한다.
Servlet을 학습하기 전에 애노테이션과 리플렉션 기술을 함께 학습하여
심도 있게 개념을 이해해보자.