꼬물꼬물

싱글톤 컨테이너 [@Configuration] 본문

스터디/스프링 핵심 원리 - 기본편

싱글톤 컨테이너 [@Configuration]

멩주 2022. 9. 18. 21:17

@Configuration과 싱글톤

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

}
  • @Bean memberService -> new MemoryMemberRepository();
  • @Bean orderService -> new MemoryMemberRepository(); / new RateDiscountPolicy();
결과적으로 다른 2개의 MemoryMemberRepository가 생성되면 싱글톤이 깨지는 것이 아닐까?

 

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

        MemberRepository memberRepository1 = memberService.getMemberRepository();
        MemberRepository memberRepository2 = orderService.getMemberRepository();

        System.out.println("memberService -> memberRepository1 = "+memberRepository1);
        System.out.println("orderService -> memberRepository2 = "+memberRepository2);
        System.out.println("memberRepository = "+memberRepository);
        // 동일하다.

        assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
        assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
    }
}
// 결과
memberRepository1 = hello.core.member.MemoryMemberRepository@25e2ab5a
memberRepository2 = hello.core.member.MemoryMemberRepository@25e2ab5a
memberRepository = hello.core.member.MemoryMemberRepository@25e2ab5a
  • memberRepository 인스턴스는 모두 같은 인스턴스를 공유해 사용된다.
  • AppConfig의 자바 코드는 각각의 2번 new MemoryMemberRepository를 호출해 다른 인스턴스 생성되는게 아닌가?

 

  • 스프링은 싱글톤을 지원한다!

 

@Configuration과 바이트코드 조작의 마법

스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해줘야 한다.

그런데 스프링이 자바 코드까지 어떻게 하기는 어렵다. 자바 코드에서 MemoroyMemberRepository가 세번 호출된다.

그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.

@Configuration을 적용한 AppConfig.class를 잘 보자!

 

    @Test
    void configurationDeep(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        AppConfig bean = ac.getBean(AppConfig.class);

        System.out.println("bean = "+bean.getClass());
    }

AppConfig도 스프링 빈이 된다. 이를 조회해 클래스 정보를 보면

bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$48c26325

순수 클래스라면 hello.core.AppConfig가 출력되어야 한다.

 

클래스 명에 xxxCGLIB가 붙으면서 복잡해졌다. 이것은 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것

 

조작한 클래스를 스프링 빈으로 설정한다.

임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다. 

예측

예측한 코드 상황

  • 스프링 컨테이너에서 찾아서 반환 == 모두 스프링 빈으로 등록된 인스턴스들
  • @Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈 반환, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다.
  • 이로 인해 싱글톤이 보장된다.
  • AppConfig@CGLIB은 AppConfig의 자식 타입으로, AppConfig 타입으로 조회가 가능하다.

 

@Configuration을 적용하지 않고, @Bean만 적용하면 어떻게 될까?

@Configuration을 붙이면 바이트코드를 조작하는 CGLIB 기술을 사용해 싱글톤을 보장하지만, @Bean만 적용한다면?

bean = class hello.core.AppConfig

순수 클래스가 스프링 컨테이너에 올라갔다.

memberRepository가 총 3번 호출되며 싱글톤이 깨진다.

그리고 생성된 3개의 객체는 각각 다른 인스턴스이다.

 

정리

  • @Bean만 사용해도 스프링 빈은 등록되나, 싱글톤은 보장하지 않는다.
    • memberRepository()처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다.
  • 크게 고민할 것 없다. 스프링 설정 정보는 항상 @Configuration을 사용하자!