Spring 핵심원리 고급편 - 리플렉션

목차

리플렉션

구체적인 클래스 타입을 알지 못해도 해당 클래스의 메소드, 타입, 변수들을 사용할 수 있도록 하는 자바 API

자바 Reflection 은 실행 중인 자바 프로그램 에서 클래스 정보에 접근하도록 하는 기술입니다.

Reflection을 사용하면 클래스나 인터페이스의 이름, 필드의 이름, 타입 및 값, 메서드의 이름, 파라미터 타입 등의 정보를 가져오고 조작할 수 있습니다. 이를 통해 객체의 생성, 필드값 설정, 메서드 호출 등을 동적으로 수행할 수 있습니다.

리플렉션 - API

클래스 정보 가져오기 - forName

Class 클래스의 forName 메서드를 사용하여 클래스 정보를 가져옵니다. 예를 들어, 다음과 같이 클래스 정보를 가져올 수 있습니다.

Class<?> myClass = Class.forName("com.example.MyClass");

메서드 정보 가져오기 - getMethod

Class 클래스의 getMethod 메서드를 사용하여 메서드 메타정보를 가져옵니다. 또한, 메서드 호출시 파라미터 값들이 필요한 경우 parameterTypes 를 사용하여 메서드의 파라미터 타입을 배열 형태로 지정할 수 있습니다.

Class<?> myClass = Class.forName("com.example.MyClass");
// 메소드 정보를 가져올 때 파라미터 정보를 배열 형태로 같이 넘겨준다.
Method myMethod = myClass.getMethod("myMethod");
// 메소드에 파라미터가 있을 경우 배열 형태로 같이 넘겨준다.
Method myMethod = myClass.getMethod("myMethod", parameterTypes);

메서드 호출하기 - invoke

Reflection을 사용하여 메서드를 호출할 때는 Method 클래스의 invoke 메서드를 사용하여 target 객체내 메서드를 호출할 수 있습니다.

Class<?> myClass = Class.forName("com.example.MyClass");
Method myMethod = myClass.getMethod("myMethod");
Object result = myMethod.invoke(target, args);

target 객체내 메소드 호출시 다음과 같이 파라미터 타입을 지정해서 호출할 수도 있습니다.

Class<?> myClass = MyClass.class;
// 메소드 호출시 파라미터가 필요한 경우 파라미터 타입을 지정해서 메소드 정보를 가져온다.
Method myMethod = myClass.getMethod("myMethod", int.class, String.class);
// 메소드 호출시 파라미터가 필요한 경우 파라미터를 배열 형태로 같이 넘겨준다.
Object result = myMethod.invoke(target, 123, "hello");

필드 정보 가져오기 - getField

Class 클래스의 getField 메서드를 사용하여 필드 정보를 가져옵니다.

Class<?> myClass = Class.forName("com.example.MyClass");
Field myField = myClass.getField("myField");

필드 값 가져오기 - get

Field 클래스의 get 메서드를 사용하여 target 객체내 필드 값 정보를 가져옵니다.

Class<?> myClass = Class.forName("com.example.MyClass");
Field myField = myClass.getField("myField");
Object fieldValue = myField.get(target);

Reflection 사용하기

@Test
void reflection0(){
Hello target = new Hello();

// 공통 로직 1 시작
log.info("start");
String result1 = target.callA(); // 호출하는 메서드만 다름
log.info("result = {}", result1);
// 공통 로직 1 종료

// 공통 로직 2 시작
log.info("start");
String result2 = target.callB(); // 호출하는 메서드마 다름
log.info("result = {}", result2);
// 공통 로직 2 종료
}

@Slf4j
static class Hello(){
public String callA(){
log.info("callA");
return "A";
}

public String callB(){
log.info("callB");
return "B";
}
}

Reflection 적용

전체 코드 흐름은 비슷하고 중간에 호출하는 메서드 하나만 다르다.

log.info("start");
String result = xxx(); // 호출하는 메서드마 다름
log.info("result = {}", result);
@Test
void reflection1() throws Exception{
// 클래스 정보
Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");

Hello target = new Hello();

// CallA 메서드 정보
Method methodCallA = classHello.getMethod("callA");
Object result1 = methodCallA.invoke(target); // 인스턴스 정보를 넘겨준다.
log.info("result = {}", result1);

// CallB 메서드 정보
Method methodCallB = classHello.getMethod("callB");
Object result2 = methodCallB.invoke(target); // 인스턴스 정보를 넘겨준다.
log.info("result = {}", result2);
}
15:14:29.958 [Test worker] INFO hello.proxy.jdkdynamic.ReflectionTest$Hello - callA
15:14:29.959 [Test worker] INFO hello.proxy.jdkdynamic.ReflectionTest - result = A
15:14:29.960 [Test worker] INFO hello.proxy.jdkdynamic.ReflectionTest$Hello - callB
15:14:29.960 [Test worker] INFO hello.proxy.jdkdynamic.ReflectionTest - result = B
@Test
void reflection2() throws Exception{
// 클래스 정보
Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");

Hello target = new Hello();

// CallA 메서드 정보
Method methodCallA = classHello.getMethod("callA");
dynamicCall(methodCallA, target);

// CallB 메서드 정보
Method methodCallB = classHello.getMethod("callB");
dynamicCall(methodCallB, target);
}

private void dynamicCall(Method method, Object target) throws InvocationTargetException, IllegalAccessException {
log.info("start");
Object result = method.invoke(target);
log.info("result = {}", result);
}

리플렉션 단점

클래스와 메서드의 메타정보를 이용한 리플렉션 기술은 런타임 에 동작하기 때문에 컴파일 시점에서 에러를 잡을 수 없다.

Share