728x90
타입 캐스팅
JDK 동적 프록시와 CGLIB를 사용해서 AOP 프록시를 만드는 방법에는 각각 장단점이 있다. JDK 동적 프록시는 인터페이스가 필수이고, 인터페이스를 기반으로 프록시를 생성한다. CGLIB는 구체 클래스를 기반으로 프록시를 생성한다
스프링이 프록시를 만들 때 제공하는 ProxyFactory에 proxyTargetClass 옵션에 따라 둘 중 하나를 선택해서 프록시를 만들 수 있다.
- proxyTargetClass=false JDK 동적 프록시를 사용해서 인터페이스 기반 프록시 생성
- proxyTargetClass=true CGLIB를 사용해서 구체 클래스 기반 프록시 생성
- 참고로 옵션과 무관하게 인터페이스가 없으면 JDK 동적 프록시를 적용할 수 없으므로 CGLIB를 사용한다.
JDK 동적 프록시 한계
@Test
public void jdkProxy() {
MemberServiceImpl target = new MemberServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.setProxyTargetClass(false); //생략시 jdk 동작 프록시
//프록시를 인터페이스로 캐스팅 성공
MemberService memberServiceProxy = (MemberService) proxyFactory.getProxy();
//JDK 동적 프록시를 구현 클래스로 캐스팅 시도 실패, ClassCastException 예외 발생
Assertions.assertThrows(
ClassCastException.class, () -> {
MemberServiceImpl castingMemberService = (MemberServiceImpl) memberServiceProxy;
});
}


- 여기서는 MemberServiceImpl 타입을 기반으로 JDK 동적 프록시를 생성했다. MemberServiceImpl 타입은 MemberService 인터페이스를 구현한다.
- 따라서 JDK 동적 프록시는 MemberService 인터페이스를 기반으로 프록시를 생성한다.
- 그런데 여기에서 JDK Proxy를 대상 클래스인 MemberServiceImpl 타입으로 캐스팅하려고 하니 예외가 발생한다. 왜냐하면 JDK 동적 프록시는 인터페이스를 기반으로 프록시를 생성하기 때문이다.
- JDK Proxy는 MemberService 인터페이스를 기반으로 생성된 프록시이다. 따라서 JDK Proxy는 MemberService로 캐스팅은 가능하지만 MemberServiceImpl 이 어떤 것인지 전혀 알지 못한다.
- 따라서 MemberServiceImpl 타입으로는 캐스팅이 불가능하다. 캐스팅을 시도하면 ClassCastException.class 예외가 발생한다.
CGLIB
@Test
public void cglibProxy() {
MemberServiceImpl target = new MemberServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.setProxyTargetClass(true); //CGLIB 프록시
//프록시를 인터페이스로 캐스팅 성공
MemberService memberServiceProxy = (MemberService) proxyFactory.getProxy();
log.info("proxy class = {}", memberServiceProxy.getClass());
//CGLIB 프록시를 구현 클래스로 캐스팅 시도 성공
MemberServiceImpl castingMemberService = (MemberServiceImpl) memberServiceProxy;
}


- MemberServiceImpl 타입을 기반으로 CGLIB 프록시를 생성했다. MemberServiceImpl 타입은 MemberService 인터페이스를 구현했다.
- CGLIB는 구체 클래스를 기반으로 프록시를 생성한다. 따라서 CGLIB는 MemberServiceImpl 구체 클래스를 기반으로 프록시를 생성한다
- 여기에서 CGLIB Proxy를 대상 클래스인 MemberServiceImpl 타입으로 캐스팅하면 성공한다. 왜냐하면 CGLIB는 구체 클래스를 기반으로 프록시를 생성하기 때문이다.
- CGLIB Proxy는 MemberServiceImpl 구체 클래스를 기반으로 생성된 프록시이다. 따라서 CGLIB Proxy는 MemberServiceImpl 은 물론이고, MemberServiceImpl 이 구현한 인터페이스인 MemberService 로도 캐스팅할 수 있다.
의존관계 주입
ProxyDIAspect
@Slf4j
@Aspect
public class ProxyDIAspect {
@Before("execution(* hello.aop..*.*(..))")
public void doTree(JoinPoint joinPoint){
log.info("[proxyDIAdvice] {}", joinPoint.getSignature());
}
}
Test
@Slf4j
@Import(ProxyDIAspect.class)
//@SpringBootTest(properties = {"spring.aop.proxy-target-class=false"})
@SpringBootTest(properties = {"spring.aop.proxy-target-class=true"})
public class ProxyDITest {
@Autowired
MemberService memberService;
@Autowired
MemberServiceImpl memberServiceImpl;
@Test
public void go(){
log.info("memberService class={}", memberService.getClass());
log.info("memberServiceImpl class={}", memberServiceImpl.getClass());
memberService.hello("hello");
}
}
- properties = {"spring.aop.proxy-target-class=false"} : application.properties 에 설정하는 대신에 해당 테스트에서만 설정을 임시로 적용한다. 이렇게 하면 각 테스트마다 다른 설정을 손쉽게 적용할 수 있다.
- spring.aop.proxy-target-class=false : 스프링이 AOP 프록시를 생성할 때 JDK 동적 프록시를 우선 생성한다. 물론 인터페이스가 없다면 CGLIB를 사용한다
- JDK 동적 프록시에 구체 클래스 타입 주입
- 타입과 관련된 예외가 발생한다. 자세히 읽어보면 memberServiceImpl에 주입되길 기대하는 타입은 hello.aop.member.MemberServiceImpl이지만 실제 넘어온 타입은 com.sun.proxy.$Proxy54이다. 따라서 타입 예외가 발생한다고 한다

- @Autowired MemberServiceImpl memberServiceImpl : JDK Proxy는 MemberService 인터페이스를 기반으로 만들어진다. 따라서 MemberServiceImpl 타입이 뭔지 전혀 모른다. 그래서 해당 타입에 주입할 수 없다.
- MemberServiceImpl = JDK Proxy 가 성립하지 않는다
- CGLIB 프록시

- @Autowired MemberService memberService : CGLIB Proxy는 MemberServiceImpl 구체 클래스를 기반으로 만들어진다. MemberServiceImpl 은 MemberService 인터페이스를 구현했기 때문에 해당 타입으로 캐스팅할 수 있다.
- MemberService = CGLIB Proxy 가 성립한다.
- @Autowired MemberServiceImpl memberServiceImpl : CGLIB Proxy는 MemberServiceImpl 구체 클래스를 기반으로 만들어진다. 따라서 해당 타입으로 캐스팅 할 수 있다.
- MemberServiceImpl = CGLIB Proxy 가 성립한다.
728x90
'스프링 핵심 원리(고급편)' 카테고리의 다른 글
| Ch12. 스프링 AOP(실전 예제) - 프록시 기술과 한계(CGLIB, 스프링 해결책) (0) | 2022.04.22 |
|---|---|
| Ch12. 스프링 AOP(실전 예제) - 프록시와 내부 호출(대안) (0) | 2022.04.22 |
| Ch12. 스프링 AOP(실전 예제) - 프록시와 내부 호출(문제) (0) | 2022.04.22 |
| Ch12. 스프링 AOP(실전 예제) - 재시도 AOP (0) | 2022.04.21 |
| Ch12. 스프링 AOP(실전 예제) - 예제 만들기 & 로그 출력 AOP (0) | 2022.04.21 |