- @Configuration과 싱글톤
- @Bean memberService 를 호출하면 memberRepository() 를 호출하면서, new MemoryMemberRepository() 호출
- @Bean orderService()를 호출하면 memberRepository()와 discountPolicy()를 호출하면서, new new MemoryMemberRepository() 와 new RateDiscountPolicy() 호출
위 과정을 살펴보면 마치 싱글톤이 깨지는 것처럼 보임. 스프링 컨테이너는 이를 어떻게 해결할까?
- 실험을 해보자 !
- 테스트용으로 MemberServiceImpl과 에 getMemberRepository() 메서드 생성
- 위 테스트 결과 세 객체가 모두 같은 객체임을 확인할 수 있다.
- AppConfig를 보면 각각 2번 new 를 통해 새로운 다른 인스턴스가 호출되어 사용되어야 할 것 같은데 왜 같은 객체인걸까?
- AppConfig에 아래와 같이 "call AppConfig.~"를 sysout하여 이 AppConfig의 각각의 메서드가 몇번 호출 되는지 직접 눈으로 확인보자.
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
System.out.println("call AppConfig.discountPolicy");
return new RateDiscountPolicy();
}
}
- 위 코드로 수정하고 ConfigurationTest를 실행하면, 그 결과는 아래와 같다.
- 실제로 호출될 거라고 생각했던 횟수와 달리 call이 세번밖에 등장하지 않았다 => 스프링 컨테이너가 어떻게든 싱글톤을 유지하고 지켜준다는 뜻 .
- 예상결과
// call AppConfig.memberService
// call AppConfig.memberRepository
// call AppConfig.memberRepository
// call AppConfig.orderService
// call AppConfig.memberRepository
- @Configuration과 바이트코드 조작의 마법
- 아래 테스트를 살펴보자.
- bean.getClass의 결과가 순수한 클래스라면 'class hello.core.AppConfig' 와 같이 출력되어야 함
- 하지만 뒤에 부가적으로 무언가가 더 붙어서 출력됨
- 내가 만든 클래스가 아니라 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig라는 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것!
- 이 임의의 다른 클래스가 싱클톤이 보장되도록 해줌.
- 그럼 실제로 호출된 클래스는 다른 클래스인데 어떻게 AppConfig.class로 조회가 되었는가?
- AppConfig를 상속받은 클래스이기 때문에 부모타입을 조회하면 자식타입이 모두 끌려나오므로 AppConfig.class로 조회를 해도 바이트코드 조작 라이브러리를 통해 생성된 임의의 다른 클래스가 호출될 수 있었던 것.
- @Configuration을 붙이지 않고 @Bean만 있으면 어떻게 될까?
- 임의의 다른 클래스가 생성되지 않음. CGLIB 사용되지 않음
- 그러면서, 내가 싱글톤이 아닐 수도 있지 않을까 의문을 제기하며 나올거라 예상했던 'call ~ '의 결과가 그대로 출력됨. ( 싱글톤이 깨짐)
- @Autowired 를 하면 의존관계 주입이라해서 이걸로 해결할 수 있음(뒤에서 더 다룰 예정)