빈 스코프

상훈·2024년 2월 1일

DI container 생성 시 빈 등록 과정

  1. Bean 객체 생성
  2. 의존 관계 주입
  3. 초기화

의존 관계 주입과 초기화는 서로 다른 과정

이후 @PostConstruct 부분이 진행된다.

이때 스프링은 하기 scope에 다음과 같이 지원

  • Singletontype
    • 스프링 컨테이너의 시작과 종료까지 유지되며 스프링이 관리
  • Prototype
    • 스프링 컨테이너는 생성, 의존 관계 주입, 초기화까 지원. 이후 관리는 클라이언트가 직접

PrototypeBean with SingletonBean

  • SingtonBean에서 PrototypeBean을 의존 관계 주입 받아 함께 사용할 때 PrototypeBean 재생성되지 않고 기존에 생성된 객체가 재사용되는 문제가 발생

  • singtonBean과 prototypeBean을 함께 사용할 때도 prototypeBean을 계속 초기화하며 사용하기 위해서는 Dependency Lookup(DL) 개념이 필요

    • 의존 관계를 외부에서 주입 받는것이 아니라 직접 필요한 의존 관계를 찾자!
  • 전체 코드

    public class PrototypeProviderTest {
         @Test
         void providerTest() {
             AnnotationConfigApplicationContext ac = new
     AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
             ClientBean clientBean1 = ac.getBean(ClientBean.class);
             int count1 = clientBean1.logic();
             assertThat(count1).isEqualTo(1);
             ClientBean clientBean2 = ac.getBean(ClientBean.class);
             int count2 = clientBean2.logic();
             assertThat(count2).isEqualTo(1);
         }
         static class ClientBean {
     
              @Autowired
             private ApplicationContext ac;
             public int logic() {
                 PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
                 prototypeBean.addCount();
                 int count = prototypeBean.getCount();
                 return count;
    } }
         @Scope("prototype")
         static class PrototypeBean {
             private int count = 0;
             public void addCount() {
                 count++;
    }
             public int getCount() {
                 return count;
    }
             @PostConstruct
             public void init() {
                 System.out.println("PrototypeBean.init " + this);
             }
             @PreDestroy
             public void destroy() {
                 System.out.println("PrototypeBean.destroy");
             }
    } }
    
  • 핵심 코드

    •  @Autowired
       private ApplicationContext ac;
       public int logic() {
           PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
           prototypeBean.addCount();
           int count = prototypeBean.getCount();
           return count;
    • 어떻게 ac 가 prototypebean.class를 등록하지도 않고 찾을 수 있을까?

      1. ac는 @Autowired를 통해 앞에서 만들어놓은 ClientBean.class, PrototypeBean.class를 등록한 DI 컨테이너를 주입 받음

        void providerTest() {
                 AnnotationConfigApplicationContext ac = new
         AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
      2. 이때 등론된 PrototypeBean.class의 scope는 "prototype"

      3. prototype은 bean 객체를 조회 할 때 생성된 bean 객체를 가져오는 것이 아닌 조회 후 가져온다.

        1. Singleton
          • ac.getBean(singleton.class)
            • 의존 관계 주입이 끝나고 DI Container에 등록된 bean 객체를 잡아온다.
        2. prototype
          • ac.getBean(prototype.class)
            • singletoneBean을 호출할 때 새로 생성된 DI Container에서 등록된 prototypeBean 객체를 잡아온다.
      4. 문제점

        • 스프링 컨테이너에 매우 의존적

        • 이로 인해 단위 테스트가 힘들어짐

          • 새로 컨테이너를 만드는 것 대신 ObjectProvider를 사용하자
          • 코드 수정
           @Autowired
           // 기존
           // private ApplicationContext ac; 
          
           // 수정
           @Autowired
           private ObjectProvider<PrototypeBean> prototypeBeanProvider;
          
           public int logic() {
               PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
               prototypeBean.addCount();
               int count = prototypeBean.getCount();
               return count;
            

Request scope

  • http 요청이 오면 각 client마다 다른 응답을 주어야함.
  • 이를 위해 protypeScope를 가진 bean 생성이 필요
  • 하지만 protypeScope는 생성 + 의존 관계 주입 + 초기화 후에 클라이언트에게 던져지고 스프링에서는 관리하지 않음
  • 이럴 경우 요청이 끝날 때까지 해당 클라이언트에게 처음 응답한 protypeBean을 관리해주는 것이 매우 어려움
  • 이를 위해서 request요청이 오고 끝날 때까지 관리할 수 있는 request scope가 필요.
profile
문송 개발자

0개의 댓글