분명 스프링 컨테이너는 싱글톤을 보장한다고 했었는데 AppConfig.java 설정 파일을 살펴보면 메소드를 호출 할 때마다 new 로 새로운 객체를 생성해서 반환해주고 있습니다.
memberService의 memberRepository도 new 로 생성하고, OrderService의 memberRepository도 new로 호출될 때 새로운 객체를 생성한다면 싱글톤이 보장될 수 없을 것으로 보이는데 이게 어떻게 가능할까??
//AppConfig.java
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
...
}
결론부터 이야기 하자면 @Configuration 어노테이션이 싱글톤을 유지할 수 있도록 도와줍니다.
스프링은 @Configuration 어노테이션이 있다면 @Bean으로 스프링 빈을 새로 등록할 때 실제 설정에 넣어놓은 클래스가 아니라 해당 클래스를 상속받은 조금 다른 클래스를 빈으로 등록합니다.
@Test
@DisplayName("스프링은 빈을 등록할 경우 클래스를 상속받아 조작한 다른 클래스를 등록한다.")
public void configurationDeep() throws Exception {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
//예상 출력: bean = class hello.core.AppConfig
//실제 출력: bean = class hello.core.AppConfig$$SpringCGLIB$$0
}
이렇게 CGLIB라는 바이트 조작 라이브러리를 통해 등록된 조금 다른 클래스가 싱글톤을 보장되도록 해줍니다.
💡 CGLIB은 이후 빈 스코프와 프록시 부분에서도 한번 언급됩니다
이미 존재하는 빈이면 존재하는 빈을 반환하고, 존재하지 않는다면 생성하고 스프링 컨테이너에 등록 후 반환해주는 것이라고 이해하면 될 것 같습니다.
당연한 이야기로 @Configuration 을 제거하고 돌리면 스프링 빈으로 등록 되지만, 메소드를 직접 호출할 경우 각각 인스턴스를 생성하게 됩니다.
정리하자면 @Configuration 어노테이션이 스프링 빈을 등록할 때 해당 객체를 상속받은 유사한 객체를 빈으로 등록하고, 해당 클래스 덕분에 싱글톤을 유지할 수 있습니다.
스프링 설정 정보는 항상 @Configuration을 사용합시다.