[spring] Configuration과 싱글톤 (스프링 기본편 by 김영한)

Configuration과 싱글톤

스프링은 정말 싱글 톤 패턴을 보장해줄까요?


AppConfig를 보면 memberService와 odrderService가 호출되면서 각각 memberRepository가 호출되고 이로인해 MemoryMemberRepository가 각각 두개 생성되는 것으로 보입니다. 이는 싱글톤이 깨지는 것인데요. 그래서 진짜로 싱글톤이 깨지는지 확인해보도록 하겠습니다.

@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();
      }
... }

같은 객체인지 확인해보기위해 객체를 출력하는 함수 getMemberRepository를 테스트용으로 잠깐 만들겠습니다.

 public class MemberServiceImpl implements MemberService {
 
        private final MemberRepository memberRepository;
      
	//테스트 용도
	public MemberRepository getMemberRepository() {
          	return memberRepository;
      }
      
  }
  
  public class OrderServiceImpl implements OrderService {
  
        private final MemberRepository memberRepository;
	
        //테스트 용도
	public MemberRepository getMemberRepository() {
          	return memberRepository;
      }
}

테스트 코드

     @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);
      }
     

출력값

memberService -> memberRepository =hello.core.member.MemoryMemberRepository@708400f6
orderService -> memberRepository =hello.core.member.MemoryMemberRepository@708400f6
memberRepository =hello.core.member.MemoryMemberRepository@708400f6

아래와 같이 스프링을 이용해서 memberService -> memberRepository, orderService -> memberRepository, memberRepository 3개의 객체를 비교해봤습니다. 모두 같은 객체인 것을 확인해 볼 수 있습니다. 자바코드상으로는 이해할 수 없지만 싱글톤이 보장되고있습니다.

AppConfig가 그대로 수행되는 것이 아닌 뭔가 중간에 스프링 컨테이너에서 내부적으로 개입이 일어나야 가능한 일로 추측해 볼 수 있습니다.




@Configuration과 바이트코드 조작

AppConfig도 ac로 등록하기 위해 넘겨줄 때 스프링빈으로 등록이 됩니다. 따라서 AppConfig 스프링 빈을 조회해보도록 하겠습니다.

   @Test
   void configurationDeep() {
     ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
      
     //AppConfig도 스프링 빈으로 등록된다.
     AppConfig bean = ac.getBean(AppConfig.class);
     
     System.out.println("bean = " + bean.getClass());

}

출력값

bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$6fd0acc1

출력값을 보면 객체뒤에 이상하게 문자들이 붙으면서 순수한 AppConfig객체와 달라진 것을 볼 수 있습니다. 이는 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig를 조작한뒤 새로운 객체를 스프링 빈으로 등록해서 사용하고 있는 것입니다. 그림으로 나타내면 아래와 같은 상황입니다.


바이트 조작은 이미 등록된 객체면 그 객체를 반환하고 아니면 객체를 등록하도록 했을 것이며 그렇기 때문에 중복으로 같은 클래스의 객체가 생기지 않아 싱글톤을 보장할 수 있는 것입니다.
이는 @Configuration이라는 어노테이션 덕분에 일어나는 일입니다.





그렇다면 아래와 같이 @Configuration을 지워본뒤 AppConfig 스프링빈을 꺼내보도록 하겠습니다

//@Configuration 삭제 
public class AppConfig {

}

출력값

bean = class hello.core.AppConfig

아까와는 다르게 바이트조작이 일어나지 않은 AppConfig가 조회됩니다. 따라서 스프링을 써도 싱글톤을 보장해주지 않습니다.

좋은 웹페이지 즐겨찾기