본문 바로가기
Learning-log/Spring & JPA

(스프링 핵심 원리 - 기본편) 9-(3) 프로토타입 스코프- 싱글톤 빈과 함께 사용시 문제점 9-(4) 프로토타입 스코프 - Provider로 해결 (5) 웹 스코프

by why제곱 2023. 4. 12.

- 프로토타입 스코프 : 싱글톤 빈과 함께 사용시 문제점

  • 스프링 컨테이너에 프로토 타입 빈을 요청하는 예제 
    • 클라이언트A addCount() -> 0에서 1로 
    • 클라이언트B addCount() -> 0에서 1로
      • count는 계속 1, 1
      • 코드
package hello.core.scope;

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

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("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");
        }
    }
}
  • 결과

  • 싱글톤에서 프로토타입 빈 사용
    • clientBean은 의존관계 자동 주입 사용. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청
    • 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환 . 프로토타입 빈의 count 필드 값은 0
    • clientBean의 프로토타입 빈을 내부 필드에서 보관(참조값을 보관)
    • 클라이언트A는 clientBean을 스프링 컨테이너에 요청해서 받음. 싱글톤이므로 항상 같은 clientBean이 반환
    • 클라이언트A는 clientBean.logic() 호출
    • clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count 증가. count 값이 1이 됨
    • 클라이언트B는 clientBean을 요청해서 받음. 싱글톤이므로 같은 clientBean반환. 
    • 중요한 점!!! clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이지, 사용할 때마다 새로 생성되는 건 아님
    • 클라이언트 B는 clientBean.logic()을 호출
    • clientBean()은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가.
  • 우리가 원하는 의도가 프로토타입이 매번 새로 생성되었으면 한다면 어떻게 해결할까 ?!! 프로토타입의 사용 목적 자체가 호출할 때마다 새로 생성되기를 원하는 것이므로 !! 
  • 참고 . 여러 빈에서 같은 프로토타입 빈을 주입받으면 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성.
    • 물론 사용할 때마다 새로 생성되는 건 아님. 우리가 원하는 목적과는 다름.

 

- 프로토타입 스코프 : 싱글톤 빈과 함께 사용시 Provider로 해결

  • 스프링 컨테이너에 요청
    • 가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때마다 @Autowired로 스프링 컨테이너에 새로 요청하는 것
    • 실행해보면 getBean을 통해 항상 새로운 프로토타입 빈이 생성
    • 의존관계를 외부에서 주입받는게 아니라 직접 필요한 의존관계를 찾는 것을 Dependency Lookup 의존관계 조회라고 함.
    • 지금 필요한 기능은 지정한 프로토 타입 빈을 컨테이너에서 대신 찾아주는 딱! DL 정도의 기능만 제공하는 무언가가 있으면 됨
  • ObjectFactory, Object
    • ClientBean에 ObjectProvider의 의존관계 주입을 받는다. 이 Provider가 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스 제공. 
    • 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환
    • ObjectFactory : getObject 하나만 제공
    • ObjectProvider : ObjectFactory 를 상속받고, 그 외 추가 편의 기능 제공
    • 스프링 프레임워크에 의존적
    • 코드
@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{
        private final PrototypeBean prototypeBean; //생성시점에 주입

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;

        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
  • JSR-330 Provider : 스프링 프레임워크에 의존적이지 않은 방법
    • 단점 : 라이브러리에 gradle 추가해줘야 함.
    • provider.get() 통해 항상 새로운 프로토타입 빈 생성 
    • provider의  get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환
    • 자바 표준, 기능이 단순해서 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워짐
  • 프로토타입 빈을 언제 사용할까 ?
    • 매번 사용할 때마다 의존관계 주입이 완료된 새로운 객체가 필요할 때 사용
    • 실무 개발에서는 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 드물다.

 

- 웹 스코프

  • 싱글톤은 스프링 컨테이너의 시작과 끝까지 함께하는 매우 긴 스코프
  • 프로토타입은 생성과 의존관계 주입, 그리고 초기화까지만 진행하는 특별한 스코프

 

  • 웹 스코프 : 웹 환경에서만 동작, 프로토타입과 달리 스프링이 해당 스코프의 종료시점까지 관리-> 종료 메서드 호출됨
  • 웹스코프 종류
    • request : HTTP요청 하나가 들어오고 나갈 때까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리됨
    • session : HTTP Session과 동일한 생명주기를 가지는 스코프
    • application : 서블릿 컨텍스트와 동일한 생명주기를 가지는 스코프
    • websocket : 웹소켓과 동일한 생명주기를 가지는 스코프