Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 인프런
- Thymeleaf
- 그리디
- SpringBoot
- Proxy
- http
- AOP
- 스프링 핵심 원리
- jpa
- Greedy
- JDBC
- 스프링
- 김영한
- Exception
- 백준
- Servlet
- 자바
- 알고리즘
- springdatajpa
- 스프링 핵심 기능
- db
- java
- transaction
- kotlin
- JPQL
- pointcut
- Spring Boot
- QueryDSL
- Android
- spring
Archives
- Today
- Total
개발자되기 프로젝트
[스프링AOP프록시기술&한계] 의존관계 주입 본문
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
'인프런 > [인프런] 스프링 핵심 원리 - 고급' 카테고리의 다른 글
[스프링AOP프록시기술&한계] 스프링의 해결책 (0) | 2022.01.12 |
---|---|
[스프링AOP프록시기술&한계] CGLIB 단점 (0) | 2022.01.12 |
[스프링AOP프록시기술&한계] Type Casting (0) | 2022.01.12 |
[스프링AOP실무주의사항] 내부호출 - 구조변경 (0) | 2022.01.11 |
[스프링AOP실무주의사항] 내부호출 - 지연조회 (0) | 2022.01.11 |
Comments