[섹션5-5] @Configuration과 싱글톤
스프링 컨테이너에서 빈을 싱글톤으로 등록하는 방법
AppConfig
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public static MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AppConfig 기반의 스프링 컨테이너를 생성한다면 @Bean으로 등록된 메서드들을 한번씩 호출해서 빈으로 등록시킬 것이다.
그런데 코드를 자세히 보면 memberService() 는 여러번 호출 된다.
- memberService 빈을 만드는 코드를 보면 memberRepository() 를 호출한다.
이 메서드를 호출하면 new MemoryMemberRepository()를 호출한다. - memberRepository 빈을 만들기 위해 memberRepository( ) 를 호출한다.
- orderService 빈을 만드는 코드도 동일하게 memberRepository() 를 호출한다.
이 메서드를 호출하면 new MemoryMemberRepository() 를 호출한다.
결과적으로 3번의 new MemoryMemberRepository를 통해 서로 다른 memberRepository 객체가 생성되는것 아닐까?
하지만 스프링은 객체를 싱글톤으로 관리해준다고 했는데....
어떻게 여러번 메서드를 호출했는데 객체는 1개 일 수 있을까?
스프링은 이 문제를 어떻게 해결해주는지 알아보자
먼저 테스트 코드를 통해 memberService 을 만들었을 때 생성된 memberRepository와
orderService를 통해 만들어진 memberRepository 와 빈으로 등록된 memberRepository 가 모두 같은 객체인지 확인해보자
ConfigurationSingletonTest
- 테스트를 위해 MemberRepository를 조회할 수 있는 기능을 추가한다. 기능 검증을 위해 잠깐 사용하는 것이니 인터페이스에 조회하는 기능까지 추가하지는 말자
- MemberServiceImpl 과 OrderServiceImpl 에 해당 클래스의 memberRepository 값을 알려주는 메서드를 추가해주자
public MemberRepository getMemberRepository() {
return memberRepository;
}
public class ConfigurationSingletoneTest {
@Test
void ConfiguraionSingletonTest(){
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);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
}
//실행결과
memberService -> memberRepositoryhello.core.member.MemoryMemberRepository@45efc20d
orderService -> memberRepositoryhello.core.member.MemoryMemberRepository@45efc20d
memberRepository = hello.core.member.MemoryMemberRepository@45efc20d
실행결과 3개의 memberRepository는 모두 같은 객체임을 알 수 있다.
스프링은 객체를 싱글톤으로 관리한다고 했으므로 당연한 결과일 수 있다.
하지만 AppConfig의 코드를 보면 new MemoryMemberRepository를 하니까 호출 할 때마다 새로운 인스턴스가 생성되어야 하는게 아닐까?
어떻게 이런일이 가능할까?
AppConfig에 호출 로그 남기기
@Configuration
public class AppConfig {
@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());
}
@Bean
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
- 각각의 메서드가 몇 번 실행되는지 확인하기 위해 한번 실행될 때 마다 출력화면에 값이 나오도록 호출코드를 추가해주자
ConfigurationSingletonTest를 실행 시킨 결과를 예상해보자면
먼저, 스프링 컨테이너가 각각 @Bean을 호출해서 스프링 빈을 생성한다.
- 스프링 컨테이너가 스프링 빈에 등록하기 위해 @Bean이 붙어있는memberRepository() 호출
- memberService() 로직에서 memberRepository() 호출
- orderService() 로직에서 memberRepository() 호출
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
물론 순서는 일치하지 않을 수 있지만, "call AppConfig.memberRepository" 가 세번 호출 되지 않을까?
//실행결과
call AppConfig.memberService
16:47:35.516 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory --
Creating shared instance of singleton bean 'memberRepository'
call AppConfig.memberRepository
16:47:35.518 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory --
Creating shared instance of singleton bean 'orderService'
call AppConfig.orderService
16:47:35.520 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory --
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
실행결과 각각의 메서드들은 모두 한번씩만 호출되었다.
어떻게 이런일이 가능할까?
코드를 보면 호출을 세 번하는데 왜 실제로는 한번만 호출될 수 있었던 걸까?
이와 관련한 내용은 다음 글에서 정리해보자