2.@Configuration과 싱글톤!

Kuno17·2023년 5월 1일
0

Spring

목록 보기
3/3
post-thumbnail

@Configuration과 싱글톤

이전에 싱글톤 패턴과 싱글톤 컨테이너를 통해서 왜 싱글톤을 사용하고 어떤점을 주의해야하는지 확인했다.
그렇다면 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();
    }
}
  • memberService 빈을 만드는 코드에서 memberRepository()호출
  • 이 메서드 호출시 new MemoryMemberRepository() 호출
  • orderService 빈을 만드는 코드에서 memberRepsoitory()호출
  • 이 메서드 호출시 new MemoryMemberRepository() 호출

서로 다른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 that static @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의 내부로직은 간략하게 예상하면 다음과 같은 순서일것

  1. 이미 스프링 컨테이너에 등록되어 있는지 확인.
  2. 있다면 찾아서 그대로 반환
  3. 없다면 기존 로직을 호출해서 새로운 인스턴스를 생성하고 스프링 컨테이너에 등록,반환

@Configuration을 사용안한다면?

@Configuration을 사용하면 CGLIB를 사용해서 싱글톤을 보장하지만, 단순히 @Bean만 사용한다면 싱글톤이 보장되지 않는 문제가 발생한다.
그러니 크게 고민할게 아니라 스프링 설정 정보는 항상 @Configuration을 사용하자

profile
자바 스터디 정리 - 하단 홈 버튼 참조.

0개의 댓글