이전에 싱글톤 패턴과 싱글톤 컨테이너를 통해서 왜 싱글톤을 사용하고 어떤점을 주의해야하는지 확인했다.
그렇다면 AppConfig.class를 살펴보면
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public static MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService OrderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
서로 다른2개의 MemoryRepository가 생성되면서 싱글톤이 깨지는것이 아닌지?
직접 확인해보자 (모르는 경우 테스트해보는게 가장 좋다~!!!!)
@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);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberService --> memberRepository = " + memberRepository1);
System.out.println("orderService --> memberRepository = " + memberRepository2);
System.out.println("memberRepository = " + memberRepository);
}
실행결과
memberService --> memberRepository = hello.core.member.MemoryMemberRepository@a43ce46
orderService --> memberRepository = hello.core.member.MemoryMemberRepository@a43ce46
memberRepository = hello.core.member.MemoryMemberRepository@a43ce46
주의! AppConfig에서 static으로 선언되어 있다면 싱글톤이 보장되지 않는다.
@Configuration
과@Bean
의 조합으로 싱글톤을 보장하는 경우는 정적이지 않은 메서드일 때입니다.- By marking this method as
static
, it can be invoked without causing instantiation of its declaring@Configuration
class, thus avoiding the above-mentioned lifecycle conflicts. Note however thatstatic
@Bean
methods will not be enhanced for scoping and AOP semantics as mentioned above.
정적 메서드에 @Bean을 사용하게 되면 싱글톤 보장을 위한 지원을 받지 못합니다.
놀랍게도 별도의 설정이 없는데 모두 같은 MemoryMemberRepository를 불러오고있다.
그렇다면 AppConfig.class에서 호출할때 어떤 클래스를 호출하는지 출력하게 만들어보자.
예상되로라면 MemberRepository를 3번 호출하게 될것이다.
@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() 메서드를 실행하면
MemberRepository를 단1번만 호출한다.
예상은 3번을 호출할것으로 예상했으나 1번만 호출한다!
어떻게된 일일까? 우선 AppConfig 클래스를 호출해보자.
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
그런대 이상한 결과가 나왔다.
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$4a5fc277
단순히 클래스를 불러온것이 아니라, CGLIB라는 이상한게 붙어있다!
알고보니 이것은 내가만든 클래스가 아니라 CGLIB라는 바이트코드 조작 라이브러리를 이용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그 클래스를 빈으로 등록한 것이다.
그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.
CGLIB의 내부로직은 간략하게 예상하면 다음과 같은 순서일것
@Configuration을 사용하면 CGLIB를 사용해서 싱글톤을 보장하지만, 단순히 @Bean만 사용한다면 싱글톤이 보장되지 않는 문제가 발생한다.
그러니 크게 고민할게 아니라 스프링 설정 정보는 항상 @Configuration을 사용하자