스프링

JDK proxy vs CGLIB proxy

2023. 5. 9. 23:00
목차
  1. 프록시 기술의 한계
  2. JDK 동적 프록시 한계
  3. CGLIB 동적 프록시
  4. 프록시 기술과 한계 - 의존관계 주입
  5. 문제 발생

프록시 기술의 한계

JDK 동적 프록시와 CGLIB를 사용해서 AOP 프록시를 만드는 방법에는 각각 장단점이 있다.

JDK 동적 프록시는 인터페이스가 필수이고, 인터페이스를 기반으로 프록시를 생성한다.

CGLIB는 구체 클래스를 기반으로 프록시를 생성한다.

 

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

ProxyFactory에 proxyTargetClass 옵션에 따라서 둘중 하나를 선택해서 프록시를 만들 수 있다.

  • proxyTargetClass = false JDK 동적 프록시를 사용해서 인터페이스 기반 프록시 생성
  • proxyTargetCalss = true [default] CGLIB를 사용해서 구체 클래스 기반 프록시 생성
  • 옵션과 무관하게 인터페이스가 없으면 JDK 동적 프록시를 사용할 수 없으므로 CGLIB를 사용한다.

JDK 동적 프록시 한계

MeberServiceImpl target = new MemberServiceImpl();
ProxyFactory proxyFactory = new FroxyFactory(target);
proxyFactory.setProxyTagrgetClass(false); //JDK 동적 프록시

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

//프록시를 구현 클래스로 캐스팅 실패, ClassCastException 예외 발생
Assertions.assertThrows(ClassCastException, () -> {
	MemberServiceImpl castingMemberService = 
    	(MemberServiceImpl) meberServiceProxy;
    }
)l;

JDK 동적 프록시 원리

여기서는 MeberServiceImpl 타입을 기반으로 JDK 동적 프록시를 생성했다. MemberServiceImpl 타입은 MemberService 인터페이스를 구현한다. 따라서 JDK 동적 프록시는 MemberService 인터페이스를 기반으로 프록시를 생성한다. 그런데 여기에서 JDK Proxy를 대상 클래스인 MemberServiceImpl 타입으로 캐스팅 하려고 하니 예외가 발생한다. 왜냐하면 JDK 동적 프록시는 인터페이스를 기반으로 프록시를 생성하기 때문이다. JDK Proxy는 MemberService 인터페이스를 기반으로 생성도니 프록시이다. 따라서 JDK Proxy MemberSerivce로 캐스팅은 가능하지만 MemberServiceImpl 타입으로는 캐스팅이 불가능하다.

CGLIB 동적 프록시

MemberServiceImpl target = new MemberServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(tareget);;
proxyFactory.setProxyTargetClass(true); //CGLIB 프록시

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

//CGLIB 프록시를 구현 클래스로 캐스팅 성공
MemberServiceImpl castingMemberService = (MemberServiceImpl)memberServiceProxy;

CGLIB 프록시 원리

MemberSerivceImpl 타입을 기반으로 CGLIB 프록시를 생성했다. MemberServiceImpl 타입은 MemberService 인터페이스를 구현했다. CGLIB는 구체 클래스를 기반으로 프록시를 생성한다. 따라서 CGLIB는 MemberServiceImpl 구체 클래스를 기반으로 프록시를 생성한다. 그렇기 때문에 CGLIB Proxy를 대상 클래스인 MemberServiceImpl 타입으로 캐스팅하면 성공한다. 따라서 CGLIB Proxy는 MemberServiceImpl은 물론이고, MemberServiceImpl이 구현한 인터페이스인 MemberService로도 캐스팅할 수 있다.

프록시 기술과 한계 - 의존관계 주입

@Slf4j
@SpringBootTest(properties = {"spring.aop.proxy-target.class=false"}) //JDK동적 프록시
@SpringBootTest(properties = {"spring.aop.proxy-target.class=true"}) //CGLIB 프록시
public class proxyDITest {

    @Autowired MemberSerivce memberSerivce;
    @Autowired MemberServiceImpl memberServiceImpl;
    
    @Test
    void go() {
    	log.info("memberService class={}, memberService.getClass());
      	log.info("memberServiceImpl class={}, memberServiceImpl.getClass());
        //memberSerivceImpl.hello("hello");
   }
}
  • @SpringBootTest(properties = {"spring.aop.proxy-target.class=false"}) : application.properties에 설정하는 대신에 해당 테스트에서만 설정을 임시로 적용한다.
  • spring.aop.proxy-target.class=false : 스프링이 AOP 프록시를 생성할 때 JDK 동적 프록시를 우선 생성한다. 물론 인터페이스가 없다면 CGLIB를 사용한다.

문제 발생

spring.aop.proxy-target-class=false

실행결과

BeanNotOfRequiredTypeException: Bean named 'memberServiceImpl' is expected to
  be of type 'hello.aop.member.MemberServiceImpl' but was actually of type
  'com.sun.proxy.$Proxy54'

  • @Autowired MemberService memberService : JDK Proxy는 MemberService 인터페이스를 기반으로 만들어진다. 따라서 해당 타입으로 캐스팅에는 문제가 없다.
  • @Autowired MemberServiceImpl memberService : 문제는 여기서 발생한다. JDK Proxy는 MemberSerivce 인터페이스를 기반으로 만들어진다. 따라서 MemberServiceImpl 타입이 뭔지 전혀 모른다. 그래서 해당 타입에 주입할 수 없다.

spring.aop.proxy-target-class=true

  • @Autowired MemberSerivce memberService : CGLIB Proxy는 MemberServiceImp 구체 클래스를 기반으로 만들어진다. MemberServiceImpl은 MemberService 인터페이스를 구현했기 때문에 해당 타입으로 캐스팅 할 수 있다.
  • @Autowired MemberServiceImpl memberSerivceImpl : CGLIB Proxy는 MemberServiceImpl 구체 클래스를 기반으로 만들어진다. 따라서 해당 타입으로 캐스팅 할 수 있다.

정리

실제로 개발할 때는 인터페이스가 있으면 인터페이스를 기반으로 의존관계 주입을 받는 것이 맞다.

DI의 장점은 DI 받는 클라이언트 코드의 변경 없이 구현 클래스를 변경할 수 있는 것이다. 이렇게 하려면 인터페이스를 기반으로 의존관계를 주입 받아야 한다. MemberServiceImpl 타입으로 의존관계 주입을 받는 것 처럼 구현 클래스에 의존관계를 주입하면 향후 구현 클래스를 변경할 때 의존관계 주입을 받는 클라이언트의 코드도 함께 변경해야 한다.

따라서 올바르게 잘 설계된 애플리케이션이라면 이런 문제가 자주 발생하지는 않는다.

 

김영한님 강의를 듣고 정리

  1. 프록시 기술의 한계
  2. JDK 동적 프록시 한계
  3. CGLIB 동적 프록시
  4. 프록시 기술과 한계 - 의존관계 주입
  5. 문제 발생
'스프링' 카테고리의 다른 글
  • @SpringBootTest, @WebMvcTest, @AutoConfigureWebTestClient
  • Environment, @Value, @ConfigurationProperties
  • 스프링 AOP
  • 동시성 문제 - ThreadLocal
규동
규동
규동
규동노트
규동
전체
오늘
어제
  • 분류 전체보기 (32)
    • 스프링 (8)
    • 자바 (5)
    • 웹 (3)
    • 코딩테스트 (1)
    • 데이터베이스 (1)
    • 인프라 (1)
    • 기록 (5)
    • 개발서적 (7)
    • 앱 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 디자인 패턴
  • 클린코드
  • Cleancode
  • JUnit
  • 국제화
  • TDD
  • 다국어
  • d-day 구하기
  • 외부설정

최근 댓글

최근 글

hELLO · Designed By 정상우.
규동
JDK proxy vs CGLIB proxy
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.