Spring 컨테이너는 어떻게 객체 인스턴스를 싱글톤으로 관리하는가?
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemoryMemberRepository 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());
}
해당 예제를 보자, 코드에서는 getBean()을 통해 memberService, orderService, memberRepository를 호출한다.
아래의 AppConfig 코드를 보면 memberService와 orderService는 memberRepository 메소드를 호출한다.
즉, 해당 코드에서는 총 3번 memberRepository()가 호출되어야 한다.
🚨
이를 테스트 해보자.
memberRepository() 메소드 호출시 메시지가 출력되어야 한다.
System.out.println("call AppConfig.memberRepository");
memberRepository()는 3번 호출되지만, 실질적으로 메시지는 단 한 번만 출력된다.
🍀
Spring은 @Configuration을 통해 싱글톤으로 객체 인스턴스를 관리한다.
AppConfig를 상속하는 임의의 클래스를 생성하여 -> 다른 클래스를 빈으로 등록한다.
만약 스프링 빈이 이미 등록되어있다면 -> 기존 빈을 리턴
빈이 등록되어 있지 X -> AppConfig 코드를 통해 빈을 생성, 컨테이너에 등록
해당 로직으로 하나의 객체 인스턴스만 생성되어 싱글톤으로 관리가 가능한 것이다.
+)
만약, @Configuration이 표시되어 있지 않다면?
싱글톤을 보장하지 않는다.
// 싱글톤 테스트 코드
public class ConfigurationSingletonTest {
@Test
void configurationTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
//모두 같은 인스턴스를 참고하고 있다.
System.out.println("memberService -> memberRepository = " +
memberService.getMemberRepository());
System.out.println("orderService -> memberRepository = " +
orderService.getMemberRepository());
System.out.println("memberRepository = " + memberRepository);
//모두 같은 인스턴스를 참고하고 있다.
assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
@Test
void configurationDepp(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
}
출력 된 메시지를 보면 모두 같은 스프링 빈을 참조하고 있다.
하지만 @Configuration을 작성하지 않으면, 모두 다른 memberRepository 빈을 참조하고 있다. MemoryMemberRepository@ 뒤의 값이 모두 다르기 때문이다.
다른 객체 인스턴스가 3개나 생성된 것이므로 -> 싱글톤이 지켜지지 않는다는 것을 알 수 있다.