리플렉션

slee2·2022년 3월 14일
0

이전에 프록시를 사용해서 코드를 손대지 않고 진행할 수 있게 되었지만, 하나의 코드당 하나의 프록시를 일일이 만들어야 하는 불편함이 존재했다.

자바가 기본으로 제공하는 JDK 동적 프록시 기술이나 CGLIB 같은 프록시 생성 오픈소스 기술을 활용하면 프록시 객체를 동적으로 만들어낼 수 있다.

JDK 동적 프록시를 이해하기 위해서는 먼저 자바의 리플렉션 기술을 이해해야 한다.

package hello.proxy.jdkdynamic;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@Slf4j
public class ReflectionTest {
    
    @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";
        }
    }
}
  • 여기서 공통 로직1과 공통 로직2를 하나의 메서드로 뽑아서 합칠 수 있을까?
  • 중간에 호출하는 메서드가 다르기 때문에 공통화하는 것은 생각보다 어렵다.
  • target.callA(), target.callB() 만 동적으로 처리할 수 있다면 해결할 수 있을 듯 하다.

요즘에는 람다로 가능하다. 하지만, 지금은 리플렉션 공부가 목적이니 사용 안할거다.

Class.forName("hello.proxy.jdkdynamic.RelectionTest$Hello") :
클래스 메타정보를 흭득한다. 참고로 내부 클래스는 구분을 위해 $를 사용한다.
classHello.getMethod("call"): 클래스의 call 메서드 메타 정보를 흭득한다.
methodCallA.invoke(target): 흭득한 메서드 메타정보로 실제 인스턴스의 메서드를 호출한다. 여기서 methodCallAHello 클래스의 callA() 라는 메서드 메타정보이다.

그런데 이 테스트는 어떤 효과가 있을까?
여기서 중요한 핵심은 클래스나 메서드 정보를 동적으로 변경할 수 있다는 점이다.

공통 로직1, 공통 로직2를 한번에 처리할 수 있는 파라미터 메서드를 만들었다.

정리
정적인 target.callA(), target.callB() 코드를 리플렉션을 사용해서 Method라는 메타정보를 추상화했다. 덕분에 공통 로직을 만들 수 있게 되었다.

주의
리플렉션을 사용하면, 동적으로 유연하게 만들 수 있다. 하지만, 런타임에 동작하기 때문에, 컴파일 시점에 오류를 잡을 수 없다.
예를 들어서 getMethod("callA")를 실수로 callB로 바꿔도 컴파일 할때는 문제가 없고, 런타임에 오류가 발생하게 된다.

런타임 오류가 나게 되면 서비스 도중에 오류가 터지는 것이기 때문에 가장 내면 안되는 오류중에 하나이다.

리플렉션은 프레임워크 개발이나 또는 매우 일반적인 공통 처리가 필요할 때 부분적으로 주의해서 사용해야 한다.

0개의 댓글