[기본기] 7-2. @Configuration 과 SingleTon

khyojun·2022년 10월 1일
0
post-thumbnail

본 게시글은 김영한님의 스프링 핵심 원리 기본편을 정리한 글입니다.


📌 AppConfig에서의 싱글톤 유지

@Configuration
public class AppConfig{
    // 싱글톤 패턴이긴한데 궁금점. memberService, orderService에서 각각 memberRepository를 호출을 하는데 이때마다 new 로 MemoryMemberRepository가 생성이 되면 싱글톤인가?
    @Bean
    public MemberService memberService(){
        //1 번 호출
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService(){
        // 1번 호출
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public MemberRepository memberRepository(){
        // 이 친구가 3번이 호출되어야 될 거 같은 느낌?
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy(){
       // return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

이때까지 앞에서 우리는 싱글톤에 대한 얘기를 하면서 스프링 컨테이너가 알아서 빈으로 등록된 객체들을 싱글톤으로 유지를 시켜준다고 했었다. 근데 궁금한게 이 코드를 보면 memberService와 orderService를 빈으로 등록을 하게 되었을때 memberRepository에서 new MemoryMemberRepository()가 적어도 2번을 호출을 당하는데 그러면 객체가 싱글톤이 아니라 다른 객체들이 생성되서 패턴을 깨뜨리는게 아닐까? 하는 생각을 해봤다.

그래서 앞선 구현체 코드에 getter를 넣어 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);
        
 Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository); 
 Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
    }

예상한 것과 달리 각자가 가리키는 memberRepository가 다 같은 객체를 가리킨다는 것을 확인할 수 있다. 이게 어떻게 가능한 일인가? 싶어서 이제는 빈이 생성될때 각자의 빈들을 호출하는 출력물을 만들어보자.

@Configuration
public class AppConfig{
    // 싱글톤 패턴이긴한데 궁금점. memberService, orderService에서 각각 memberRepository를 호출을 하는데 이때마다 new 로 MemoryMemberRepository가 생성이 되면 싱글톤인가?
    @Bean
    public MemberService memberService(){
        //1 번 호출
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService(){
        // 1번 호출
        System.out.println("call AppConfig.orderService");
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public MemberRepository memberRepository(){
        // 이 친구가 3번이 호출되어야 될 거 같은 느낌?
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy(){
       // return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

이렇게 될 경우

이렇게 단 한 번씩만 호출이 되고 다음은 호출이 없다. ??!

@Configuration과 바이트코드 조작

위 처럼 예상했던 대로라면 memberRepository call을 총 3번이 출력이 되었어야 하는데 이게 웬일 한 번만 호출을 한다. 이것에 대한 진실은 바로 @Configuration이 적용된 AppConfig에 있다고 하는데 다음 코드를 한 번 출력을 시켜보자.

  @Test
    void configurationTestDeep(){
        // bean 등록되지 않은 순수 클래스인 경우
        AppConfig appConfig = new AppConfig();
        System.out.println("appConfig = " + appConfig.getClass());

        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        
        //bean 불렀을떄
        AppConfig bean = ac.getBean(AppConfig.class);

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

    }

빈 등록이 되지 않았을때 순수한 클래스 호출

빈 등록이 된 클래스 호출

위의 경우가 일반적으로 보인 normal한 경우이고 밑에 bean을 보게 되면 뒤에 $$EnhancerBySpringCGLIB 뭐시기뭐시기 가 더 있다. 여기서 CGLIB 이 친구가 중요한 핵심이다. 요 친구가 그 바이트코드 조작 라이브러리인데 AppConfig가 아니라 실제로는 이 클래스를 상속받은 다른 임의의 클래스를 생성하고 그것을 빈으로 등록을 실제로 한 거다.

그니까 쉽게 말하자면 그림자 분신술처럼 가짜 AppConfig를 만들어서 빈으로 등록을 한 거다.

요런 느낌?

그리고 이런 CGLIB는 아마 이런 식으로 동작을 한다고 한다.

빈 A가 이미 스프링 컨테이너에 등록이 되어있다면? -> 스프링 컨테이너에서 찾아서 반환
없다면? -> 기존 로직을 호출하고 객체를 생성한 후 스프링 컨테이너에 등록

그니까 없으면 생성 있으면 컨테이너에서 땡겨오기라고 보면 될 거 같다. 이런 것들이 @Bean이 붙어있는 메서드마다 작동한다고 보면 될 거 같다. 이러한 것 덕분에 싱글톤이 잘 보장이 된다는 것을 알 수 있다.

@Configuration이 없다면

그치만 위의 어노테이션이 없다면 아무 의미가 없다. 실제로 다시 원래대로 돌아가게 되는데

이렇게 아까와 같이 싱글톤이 유지되어지지 않고 여러 객체가 생성이 되었다는 것을 알 수 있다.

오늘의 결론.

스프링 설정 정보 쓸 거면 @Configuration 무조건 붙이자. 알아서 다 해준다.

출처

  1. 김영한님의 스프링 핵심 원리 기본편(https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8)
profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

0개의 댓글