본문 바로가기
Spring/Basic

프로토타입 스코프

by o3oppp 2024. 9. 1.
빈 스코프
  • 스코프는 빈이 존재할 수 있는 범위를 뜻함
  • 스프링은 다음과 같은 스코프를 지원
    1.    싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
    2.    프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하는 매우 짧은 범위의 스코프
    3.    웹 관련 스코프 : request, session, apllication

프로토타입 스코프

인프런 참조

  • 프로토타입 스코프의 빈을 스프링 컨에티너에 요청
  • 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 의존관계 주입

인프런 참조

  • 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
  • 이후 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환

Test

public class PrototypeTest {

    @Test
    void prototypeBeanFind(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        System.out.println("find prototypeBean1");
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        System.out.println("find prototypeBean2");
        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        System.out.println("prototypeBean1 = " + prototypeBean1);
        System.out.println("prototypeBean2 = " + prototypeBean2);
        assertThat(prototypeBean1).isNotSameAs(prototypeBean2);

        //prototypeBean1.destroy();
        //prototypeBean2.destroy();
        ac.close();
    }

    @Scope("prototype")
    static class PrototypeBean{
        @PostConstruct
        public void init(){
            System.out.println("PrototypeBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("PrototypeBean.destroy");
        }
    }
}

Test 결과

  • 프로토타입 빈을 2번 조회했으므로 완전히 다른 스프링 빈이 생성되고, 초기화도 2번 실행
  • 프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입, 초기화 까지만 관여하므로 @PreDestroy같은 종료 메서드를 실행하지 않음(프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있음)

싱글톤과 프로토타입 동시 사용시 문제점

  • clientBean은 싱글톤이므로 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생
  • 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요창
  • 스프링 컨테이너는 프로토타입 빈을 생성한 후 clientBean에 반환(프로토타입 빈의 count 필드 값은 0)
  • clientBean은 PrototypeBean을 내부 필드에 보관(참조값을 보관)

  • 클라이언트A는 clientBean을 스프링 컨테이너에 요청하여 받음(싱글톤이므로 항상 같은 clientBean 반환)
  • 클라이언트A가 clientBean.logic() 호출
  • clientBean은 PrototypeBean의 addCount()를 호출해서 PrototypeBean의 count를 증가하여 count값이 1이 됨

  • 클라이언트B는 clientBean을 스프링 컨테이너에 요청하여 받음(싱글톤이므로 항상 같은 clientBean 반환)
  • 클라이언트B가 clientBean.logic() 호출
    • clientBean이 내부에 가지고 있는 PrototypeBean은 이미 과거에 주입이 끝난 빈
    • 주입 시점에 스프링 컨테이너에 요청하여 프로토타입 빈이 새로 생성된 것이지 사용 시 마다 새로 생성되는 것은 아님
  • clientBean은 PrototypeBean의 addCount()를 호출해서 PrototypeBean의 count를 증가하여 count값이 2가 됨
    • 싱글톤 빈은 생성시점에만 의존관계 주입을 받기 때문에 프로토타입 빈이 새로 생성되기는 하지만 싱글톤 빈과 함께 계속 유지되는 문제 발생

싱글톤과 프로토타입 동시 사용시 문제해결

1. ObjectProvider

  • 지정된 빈을 컨테이너에 대신 찾아주는 DL 서비스를 제공
  • 과거에는 ObjectFactory를 사용하였지만, 여기에 편의 기능을 추가한 것이 ObjectProvider
public class SingletonWithPrototypeTest1 {

    @Test
    void singletonClientUsePrototype() {
        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);
    }

    @Scope("singleton")
    static class ClientBean {

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            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");
        }
    }
}

프로토타입 빈 조회

  • getObject()를 통해 항상 새로운 프로토타입 빈이 생성되는 것을 확인

2. JSR-330 Provider

  • 자바 표준을 사용하는 방법으로 스프링이 아닌 다른 컨테이너에서도 사용 가능
public class SingletonWithPrototypeTest1 {
    
    @Test
    void prototypeFind(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        prototypeBean1.addCount();
        assertThat(prototypeBean1.getCount()).isEqualTo(1);

        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        prototypeBean2.addCount();
        assertThat(prototypeBean2.getCount()).isEqualTo(1);
    }

    @Scope("singleton")
    static class ClientBean {

        @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;

        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            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");
        }
    }
}

프로토타입 빈 조회

  • get()을 통해 항상 새로운 프로토타입 빈이 생성되는 것을 확인

'Spring > Basic' 카테고리의 다른 글

빈 생명주기 콜백  (1) 2024.09.01
의존관계 자동 주입  (0) 2024.08.29
@ComponentScan  (0) 2024.08.21
@Configuration  (0) 2024.08.20
Singleton  (0) 2024.08.20