개발언어/JAVA

[Reflection] 자바가 동적으로 Class, Method의 메타데이터를 획득하여 작동시키는 방법

nomoreFt 2022. 11. 16. 22:53

동적 프록시

프록시를 동작을 원하는 개수만큼 생성해놓는게 아니라,

동적으로 객체를 만들 수 있다.

프록시를 적용할 코드를 하나 만들고, 동적 프록시 기술로 원하는 만큼 찍어내면 된다.


그러기 위해서는 기본적으로 자바가 어떤 클래스, 어떤 메서드던지 획득, 작동을 시킬 수 있어야 한다.

그래서 자바에서는 Reflection API를 제공한다.


Reflection API


대표적으로 package명으로 Class를 가져오고,
이름으로 해당 Class의 Method명을 가져오면 된다.

개인적으로는 해당 Class의 Method를 전부 가져와서
Enum으로 메서드명을 value로 가져와서 일치하면 실행시키는 식으로 개발했었다.

예시

@Slf4j
public class ReflectionTest {

    /**
     * Reflection이 필요한 상황
     */
    @Test
    void reflection0() {
        Hello target = new Hello();

        /**
         * 호출하는 메서드만 다르고 나머지는 동일한 동작.
         * 공통 로직 1과 2를 하나의 메서드로 뽑아서 합칠 수 있을까?
         */
        //공통 로직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 종료

    }

    /**
     * Reflection을 사용.
     * 클래스 정보를 획득하여 사용 예제
     */

    @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("result1={}", result1);

        //callB 메서드 정보
        Method methodCallB = classHello.getMethod("callB");
        Object result2 = methodCallB.invoke(target);
        log.info("rsult2={}", result2);
    }

    /**
     * Reflection 을 사용해 더 동적으로 만들어보기
     */
    @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 Exception{
        log.info("start");
        Object result = method.invoke(target);
        log.info("result={}", result);

    }

    /**
     * Runtime에 동작하기 때문에 컴파일 시점에 오류를 잡기 힘들다.
     * 그래서 일반적으로 사용하지 않으면 좋다.
     * 너무 일반적인 공통 처리가 필요할 때 부분적으로 주의해서 사용해야 한다.
     */
    @Slf4j
    static class Hello {
        public String callA() {
            log.info("callA");
            return "A";
        }

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

Reflection 주의사항

  • Runtime에 동작하기 때문에 컴파일 시점에 오류를 잡기 힘들다.
  • 그래서 일반적으로 사용하지 않으면 좋다.
  • 너무 일반적인 공통 처리가 필요할 때 부분적으로 주의해서 사용해야 한다.