@Configuration과 싱글톤

현시기얌·2022년 3월 3일
0

Spring 핵심원리

목록 보기
6/15

예제 코드

@Configuration
public class AppConfig {

    //@Bean memberService -> new MemoryMemberRepository()
    //@Bean orderService -> new MemoryMemberRepository()
    

    @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
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
  • memberService 빈을 만드는 코드를 보면 memberRepository()를 호출한다.
    • 이 메소드를 호출하면 new MemoryMemberRepository()를 호출한다.
  • orderService 빈을 만드는 코드도 동일하게 memberRepository를 호출한다.
    • 이 메소드를 호출하면 new MemoryMemberRepository()를 호출한다.
  • 결과적으로 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는 것 처럼 보인다.

테스트 코드

    @Test
    void configurationTest() {
        //given
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        final MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
        final OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
        final MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
        //when
        final MemberRepository memberRepository1 = memberService.getMemberRepository();
        final MemberRepository memberRepository2 = orderService.getMemberRepository();
        //then
        assertThat(memberRepository1).isSameAs(memberRepository2);
        assertThat(memberRepository).isSameAs(memberRepository1);
        assertThat(memberRepository).isSameAs(memberRepository2);
    }
  • 확인해보면 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.
  • AppConfig의 자바 코드를 보면 분명히 각가 2번 new MemoryMemberRepository가 호출되서 다른 인스턴스가 생겨야 하는데 말이다.

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

  • 스프링 컨테이너는 싱글톤 레지스트리다.
    따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다.
    그런데 스프링이 자바 코드까지 어떻게하기는 어렵다.
  • 따라서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.

예제 코드

    @Test
    void configurationDeep() {
        //given && when
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        final AppConfig bean = ac.getBean(AppConfig.class);
        //then
        System.out.println("bean.getClass() = " + bean.getClass());

    }

실행 결과

bean.getClass() = class hyun6ik.corerepeat.config.AppConfig$$EnhancerBySpringCGLIB$$ef138311

  • 예상 했던 결과 : class hyun6ik.corerepeat.config.AppConfig
  • 그런데 예상과는 다르게 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다.
    이것은 스프링이 CGLIB 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것이다. (프록시)

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

AppConfig CGLIB 슈도 코드

@Bean
public MemberRepository memberRepository() {
    if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면 ) {
        return 스프링 컨테이너에서 찾아서 반환;
    }
    기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록 한 후 
    return 반환
}
  • @Bean이 붙은 메소드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다.
  • 덕분에 싱글톤이 보장된다.

cf) AppConfig@CGLIB는 AppConfig의 자식 타입 이므로 AppConfig 타입으로 조회할 수 있다.

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

  • 순수한 AppConfig로 스프링 빈에 등록이 된다.
  • 하지만 싱글톤을 보장하지 못한다.
  • 스프링 컨테이너에서 관리해주지 않는다.

@Configuration 정리

  • @Bean만 사용해도 스프링 빈으로 등록되지만 싱글톤을 보잫하지 않는다.
    • memberRepository() 처럼 의존관계 주입이 필요해서 메소드를 직접 호출할 때 싱글톤을 보장하지 않는다.
  • 때문에 스프링 설정 정보는 항상 @Configuration을 사용하자.
profile
현시깁니다

0개의 댓글