Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

[스프링AOP프록시기술&한계] 의존관계 주입 본문

인프런/[인프런] 스프링 핵심 원리 - 고급

[스프링AOP프록시기술&한계] 의존관계 주입

Seung__ 2022. 1. 12. 22:28

JDK 동적 프록시를 사용하면서 의존관계 주입을 할 때 어떤 문제가 발생하는지 코드로 알아보자.

 

1. ProxyDIAspect, Test


@Slf4j
@Aspect
public class ProxyDIAspect {

    @Before("execution(* hello.aop..*.*(..))")
    public void doTrace(JoinPoint joinPoint){
    log.info("[proxyDIAdvice] {}", joinPoint.getSignature());
    }
}
@Slf4j
@SpringBootTest(properties = {"spring.aop.proxy-target-class=false"}) //jdk 동적 프록시
@Import(ProxyDIAspect.class)
public class ProxyDITest {

    @Autowired
    MemberService memberService;

    @Autowired
    MemberServiceImpl memberServiceImpl;

    @Test
    void go(){
        log.info("memberService class={}", memberService.getClass());
        log.info("memberServiceImpl class={}", memberServiceImpl.getClass() );
    }
}

 

  • @SpringBootTest
    • 내부에 컴포넌트 스캔을 포함하고 있다.
    • MemberServiceImpl 에 @Component 가 붙어있으므로 스프링 빈 등록 대상이 된다.
  • properties = {"spring.aop.proxy-target-class=false"}
    • application.properties 에 설정하는 대신에 해당 테스트에서만 설정을 임시로 적용한다. 
    • 이렇게 하면 각 테스트마다 다른 설정을 손쉽게 적용할 수 있다.
  • spring.aop.proxy-target-class=false
    • 스프링이 AOP 프록시를 생성할 때 JDK 동적 프록시를 우선 생성한다. 
    • 물론 인터페이스가 없다면 CGLIB를 사용한다.
  • @Import(ProxyDIAspect.class)
    • 앞서 만든 Aspect를 스프링 빈으로 등록한다.

 

 

 

2. JDK 동적 프록시에 구체 클래스 타입 주입


JDK 동적 프록시에 구체 클래스 타입을 주입할 때 어떤 문제가 발생??

 

  • 먼저 spring.aop.proxy-target-class=false 설정을 사용해서 스프링 AOP가 JDK 동적 프록시를 사용하도록 했다. 
  • 이렇게 실행하면 다음과 같이 오류가 발생한다.
BeanNotOfRequiredTypeException: Bean named 'memberServiceImpl' is expected to be of type 'hello.aop.member.MemberServiceImpl' but was actually of type 'com.sun.proxy.$Proxy54'
  • 타입과 관련된 예외가 발생한다. 자세히 읽어보면 memberServiceImpl 에 주입되길 기대하는 타입은
    hello.aop.member.MemberServiceImpl 이지만 실제 넘어온 타입은 com.sun.proxy.$Proxy54 이다.
  • 따라서 타입 예외가 발생한다고 한다.
  • JDK 동적 프록시는 Interface기반 프록시이다. 따라서 생성된 프록시는 구체 클래스를 알 수 가 없다.
  • 따라서 인터페이스 기반 프록시를 구체 클래스인 MemberServiceImple에 의존관계 주입이 불가능.

  • @Autowired MemberService memberService
    • 이 부분은 문제가 없다. JDK Proxy는 MemberService 인터페이스를 기반으로 만들어진다. 
    • 따라서 해당 타입으로 캐스팅 할 수 있다.
    • MemberService = JDK Proxy 가 성립한다.
  • @Autowired MemberServiceImpl memberServiceImpl
    • 문제는 여기. JDK Proxy는 MemberService 인터페이스를 기반으로 만들어진다. 
    • 따라서 MemberServiceImpl 타입이 뭔지 전혀 모른다. 
    • 그래서 해당 타입에 주입할 수 없다.
    • MemberServiceImpl = JDK Proxy 가 성립하지 않는다.

 

 

3. CGLIB 사용, 의존관계 주입


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

    @Autowired
    MemberService memberService;

    @Autowired
    MemberServiceImpl memberServiceImpl;

    @Test
    void go(){
        log.info("memberService class={}", memberService.getClass());
        log.info("memberServiceImpl class={}", memberServiceImpl.getClass() );
    }
}

정상적으로 동작한다!

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

 

 

4. 정리


  • JDK 동적 프록시는 대상 객체인 MemberServiceImpl 타입에 의존관계를 주입할 수 없다.
  • CGLIB 프록시는 대상 객체인 MemberServiceImpl 타입에 의존관계 주입을 할 수 있다.
  • 지금까지 JDK 동적 프록시가 가지는 한계점을 알아보았다. 
  • 실제로 개발할 때는 인터페이스가 있으면  인터페이스를 기반으로 의존관계 주입을 받는 것이 맞다.
  • DI의 장점이 무엇인가? DI 받는 클라이언트 코드의 변경 없이 구현 클래스를 변경할 수 있는 것이다. 
  • 이렇게  하려면 인터페이스를 기반으로 의존관계를 주입 받아야 한다.
  • MemberServiceImpl 타입으로 의존관계  주입을 받는 것 처럼 구현 클래스에 의존관계를 주입하면 향후 구현 클래스를 변경할 때 의존관계 주입을받는 클라이언트의 코드도 함께 변경해야 한다.
  • 따라서 올바르게 잘 설계된 애플리케이션이라면 이런 문제가 자주 발생하지는 않는다.
  • 그럼에도 불구하고 테스트, 또는 여러가지 이유로 AOP 프록시가 적용된 구체 클래스를 직접 의존관계 주입
    받아야 하는 경우가 있을 수 있다. 
  • 이때는 CGLIB를 통해 구체 클래스 기반으로 AOP 프록시를 적용하면  된다.
  • 여기까지 듣고보면 CGLIB를 사용하는 것이 좋아보인다. 
  • CGLIB를 사용하면 사실 이런 고민 자체를 하지 않아도 된다. 흠.?

 

5. GitHub: 220112 Proxy & Dependency Injection


 

GitHub - bsh6463/SpringAOP

Contribute to bsh6463/SpringAOP development by creating an account on GitHub.

github.com

 

Comments