프록시 기술과 한계 - 타입 캐스팅

현시기얌·2021년 12월 6일
0

AOP

목록 보기
16/19

프록시 시술과 한계 - 타입 캐스팅

JDK 동적 프록시와 CGLIB를 사용해서 AOP 프록시를 만드는 방법에는 각각 장단점이 있다.
JDK 동적 프록시는 인터페이스가 필수이고, 인터페이스 기반으로 프록시를 생성한다.
CGLIB는 구체 클래스를 기반으로 프록시를 생성한다.

물론 인터페이스가 없고 구체클래스가 있는 경우에만 CGLIB를 사용해야 한다.
그런데 인터페이스가 있는 경우에는 JDK 동적 프록시나 CGLIB 둘중에 하나를 선택할 수 있다.

스프링이 프록시를 만들 때 제공하는 ProxyFactory에 proxyTargetClass 옵션에 따라 둘 중 하나를 선택해서 프록시를 만들 수 있다.

  • proxyTargetClass=false : JDK 동적 프록시를 사용해서 인터페이스 기반 프록시 생성
  • proxyTargetClass=true : CGLIB를 사용해서 구체 클래스 기반 프록시 생성

JDK 동적 프록시의 한계

인터페이스 기반으로 프록시를 생성하는 JDK 프록시는 구체 클래스로 타입 캐스팅이 불가능한 한계가 있다.

예제 코드

@Slf4j
@SpringBootTest
public class ProxyCastingTest {

    @Test
    void jdkProxy() {
        final MemberServiceImpl target = new MemberServiceImpl();
        final ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(false); // JDK 동적 프록시

        // 프록시를 인터페이스로 캐스팅 성공
        final MemberService memberServiceProxy = (MemberService) proxyFactory.getProxy();

        // JDK 동적 프록시를 구현 클래스로 캐스팅 시도 실패, ClassCaseException 예외 발생
        assertThrows(ClassCastException.class, () -> {
            final MemberServiceImpl castingMemberService = (MemberServiceImpl) memberServiceProxy;
        });
    }
}

위의 코드는 MemberServiceImpl 타입을 기반으로 JDK 동적 프록시를 생성했다.
MemberServiceImpl 타입은 MemberService 인터페이스를 구현한다.
따라서 JDK 동적 프록시는 MemberService 인터페이스 기반으로 프록시를 생성한다.
이 프록시를 JDK Proxy라고 하자.

JDK 동적 프록시 캐스팅

그런데 여기서 JDK Proxy를 대상 클래인 MemberServiceImpl타입으로 캐스팅하려고하니 예외가 발생한다.
왜냐하면 JDK 동적 프록시는 인터페이스를 기반으로 프록시를 생성하기 때문이다.
JDK Proxy는 MemberService 인터페이스를 기반으로 생성된 프록시이다.
따라서 JDK Proxy는 MemberService로 캐스팅은 가능하지만 MemberServiceImpl이 어떤 것인지 전혀 알지 못한다.
그러므로 MemberServiceImpl 타입으로는 캐스팅이 불가능하다.
캐스팅을 시도하면 ClassCastException.class 예외가 발생한다.

CGLIB 프록시

예제 코드

    @Test
    void cglibProxy() {
        final MemberServiceImpl target = new MemberServiceImpl();
        final ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(true); // CGLIB 프록시

        // 프록시를 인터페이스로 캐스팅 성공
        final MemberService memberServiceProxy = (MemberService) proxyFactory.getProxy();

        // CGLIB 프록시를 구현 클래스로 캐스팅 시도 성공
        final MemberServiceImpl castingMemberService = (MemberServiceImpl) memberServiceProxy;
        assertThat(AopUtils.isCglibProxy(castingMemberService)).isTrue();
    }

MemberServiceIMpl 타입을 기반으로 CGLIB 프록시를 생성했다.
MemberServiceImpl 타입은 MemberService 인터페이스를 구현했다.
CGLIB는 구체 클래스를 기반으로 프록시를 생성한다.
따라서 CGLIB는 구체 클래스를 기반으로 프록시를 생성한다.
이 프록시를 CGLIB Proxy라고 하자.

CGLIB 프록시 캐스팅

여기에서 CGLIB Proxy를 대상 클래스인 MemberSeriveImpl 타입으로 캐스팅하면 성공한다.
왜냐하면 CGLIB는 구체 클래스를 기반으로 프록시를 생성하기 때문이다.
CGLIB Proxy는 MemberServiceImpl 구체 클래스를 기반으로 생성된 프록시이다.
따라서 CGLIB Proxy는 MemberServiceIMpl은 물론이고 memberServiceIMpl이 구현한 인터페이스인 MemberService로도 캐스팅 할 수 있다.

정리

JDK 동적 프록시는 대상 객체인 MemberServceImpl로 캐스팅 할 수 없다.
CGLIB 프록시는 대상 객체인 MemberServiceIMpl로 캐스팅 할 수 있다.
그리고 이 문제는 의존 관계 주입시에 발생하게 된다.

profile
현시깁니다

0개의 댓글