본문 바로가기
혼자 공부하는 것들/Spring

Reflection(리플렉션) 활용

by applepick 2022. 5. 22.
반응형

 

출처:https://www.geeksforgeeks.org/reflection-in-java/

앞서 프록시를 사용해서 기존 코드를 변경하지 않고 부가적인 기능들을 추가할 수 있는 방법을 배웠습니다. 앞 게시글에서 보았듯이 프록시만 적용한다면 해당 클래스 수만큼 부가적인 기능을 위한 프록시 클래스를 만들어야 한다는 단점이 있습니다. 이러한 단점을 보안하기 위해 자바에서 기본적으로 제공하는 JDK 동적 프록시 기술이나 CGLIB(개발자는 굳이 사용할 일이 없지만 그래도 알아놓자!) 같은 프록시 생성 오픈소스 기술을 사용하면 객체를 동적으로 만들 수 있습니다. 간단하게 리플렉션을 설명하자면 클래스나 메서드의 메타정보를 사용해서 동적으로 호출하는 메서드를 유연하게 끼워 넣을 수 있습니다. 

 

https://applepick.tistory.com/160

 

인터페이스 기반 프록시 VS 클래스 기반 프록시

https://applepick.tistory.com/159 프록시 패턴과 데코레이터 패턴 인프런 스프링 고급 원리를 들으면서 프록시 패턴과 데코레이터 패턴을 공부하고 있습니다. "둘이 뭔가 비슷한데? 뭐가 다른거지? 정확

applepick.tistory.com

예제 코드:

@Slf4j
public class ReflectionTest {

    @Test
    void reflection1() throws Exception {
        //클래스 메타정보를 획득한다. 참고로 내부 클래스는 구분을 위해 $ 를 사용한다.
        Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");

        Hello target = new Hello();
        //callA 메서드 정보
        
        Method methodCallA = classHello.getMethod("callA");
        //획득한 메서드 메타정보로 실제 인스턴스의 메서드를 호출한다. 여기서 methodCallA 는 Hello 클래스의 callA() 이라는 메서드 메타정보이다.
        
        Object result1 = methodCallA.invoke(target);
        //methodCallA.invoke(인스턴스) 를 호출하면서 인스턴스를 넘겨주면 해당 인스턴스의 callA() 메서드를 찾아서 실행한다. 여기서는 target 의 callA() 메서드를 호출한다.
        log.info("result1={}", result1);

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

    @Test
    void reflection2() throws Exception {
        Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");
        Hello target = new Hello();
        Method methodCallA = classHello.getMethod("callA");
        dynamicCall(methodCallA, target);
        Method methodCallB = classHello.getMethod("callB");
        dynamicCall(methodCallB, target);
    }

    /**
     * 공통 로직1, 공통 로직2를 한번에 처리할 수 있는 통합된 공통 처리 로직
     * @param method
     * @param target
     * @throws Exception
     */
    private void dynamicCall(Method method, Object target) throws Exception {
        log.info("start");
        Object result = method.invoke(target);
        log.info("result={}", result);
    }

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

}

 간단한 예제코드를 보면서 리플렉션을 정리해보았습니다. target.callA()와 target.callB()의 코드를 리플랙션을 사용하여 Method라는 메타정보로 추상화시켜서 빼낼 수 있었습니다. 

 

리플랙션을 사용해서 클래스의 메서드의 메타정보를 사용해서 동적으로 유연하게 코드를 적용시킬 수 있었습니다. 하지만, 단점으로는 리프렉션 기술은 런타임에 작동하기 때문에, 컴파일 시점에 오류를 잡을 수 없습니다...ㅜㅜ 예를 들어 리플렉션을 적용해도 컴파일 시점에 오류를 내뱉지 않고 사용자가 그 기능을 실행할 시점에 오류를 발생합니다. 만약 복잡하게 프록시가 엮여있다면, 로그를 분석해서 찾아내야 합니다. ㅠㅠ 단적인 예로 위에 예제 코드에서 getMethod("정상 메서드")를 getMethod("비정상 메서드")로 잘못 기입하여도 컴파일 시점에 찾아낼 수가 없습니다. 그러기 때문에 확실히 개선할 수 있는 상황이아니면 리플렉션은 지양해야합니다.

반응형

댓글