ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 14. @Configuration과 싱글톤, 바이트코드 조작의 마법
    Spring/스프링 핵심 원리 - 기본편 2023. 4. 24. 21:45

    @Configuration과 싱글톤

     

    @Bean memberService -> new MemoryMemberRepository()

    @Bean orderService -> new MemoryMemberRepository()

     

    • memberService 빈을 만드는 코드를 보면 memberRepository() 를 호출한다.
      • 이 메서드를 호출하면 new MemoryMemberRepository() 를 호출한다.
    • orderService 빈을 만드는 코드도 동일하게 memberRepository() 를 호출한다.
      • 이 메서드를 호출하면 new MemoryMemberRepository() 를 호출한다. 

    결과적으로 각각 다른 2개의 MemoryMemberRepository 가 생성되면서 싱글톤이 깨지는 것 처럼 보인다. 스프링 컨테이너는 이 문제를 어떻게 해결할까

     

     

     

    package hello.core.singletonTest;

    import hello.core.AppConfig;
    import hello.core.member.MemberRepository;
    import hello.core.member.MemberServiceImpl;
    import hello.core.order.OrderServiceImpl;
    import org.junit.jupiter.api.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;

    import static org.assertj.core.api.Assertions.*;

    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 -> memberRepository = " + memberRepository1);
            System.out.println("orderService -> memberRepository = " + memberRepository2);
            System.out.println("memberRepository = " + memberRepository);
            
            assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
            assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
        }
    }

    test/java/hello.core/singletonTest/ConfigurationSingletonTest 생성

     

    memberRepository1, memberRepository2, memberRepository 같음

     

    • 확인해보면 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.
    • AppConfig의 자바 코드를 보면 분명히 각각 2번 new MemoryMemberRepository 호출해서 다른 인스턴스가 생성되어야 하는데?

     

    1. call AppConfig.memberService
    2. call AppConfig.memberRepository
    3. call AppConfig.memberRepository
    4. call AppConfig.orderService
    5. call AppConfig.memberRepository

    1번부터 5번 순으로 출력되어야 할 것처럼 보임(call AppConfig.memberRepository 세번 출력)

     

    한번씩 출력 됨

     

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

     

    • 스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다.
    • 그런데 스프링이 자바 코드까지 어떻게 하기는 어렵다. 저 자바 코드를 보면 분명 3번 호출되어야 하는 것이 맞다.
    • 그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.
    • 모든 비밀은 @Configuration 을 적용한 AppConfig 에 있다.

     

    @Test
    void configrutaionDeep() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        AppConfig bean = ac.getBean(AppConfig.class);
        
        System.out.println("bean = " + bean.getClass());
    }

    • 사실 AnnotationConfigApplicationContext 에 파라미터로 넘긴 값은 스프링 빈으로 등록된다. 그래서 AppConfig 도 스프링 빈이 된다.

     

    • 순수한 클래스라면 class hello.core.AppConfig가 출력되어야 한다.
    • 그런데 예상과는 다르게 클래스 명에 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다.
    • 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다!

     

    그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다. 아마도 다음과 같이 바이트 코드를 조작해서 작성되어 있을 것이다.(실제로는 CGLIB의 내부 기술을 사용하는데 매우 복잡하다.)

     

    AppConfig@CGLIB 예상 코드

    @Bean
    public MemberRepository memberRepository() {
        if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
            return 스프링 컨테이너에서 찾아서 반환;
        } else { //스프링 컨테이너에 없으면
            기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
            return 반환
        }
    }

    스프링 컨테이너에 없으면 내가 만든 AppConfig.java의 memberRepository()을 호출해서 스프링 컨테이너에 등록

    스프링 컨테이너에 있으면 있는 로직 반환

     

    ※ 참고 AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회 할 수 있다.

     

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

    @Configuration 을 붙이면 바이트코드를 조작하는 CGLIB 기술을 사용해서 싱글톤을 보장하지만, 만약 @Bean만 적용하면 어떻게 될까?

     

    @Configuration 주석 처리

     

    싱글톤이 깨져버려서 memberRepository 3번 호출 됨

     

    memberRepository 3개가 다름

     

    정리

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

     

    출처 : 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

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런

    www.inflearn.com

Designed by Tistory.