- 프로토타입 스코프 : 싱글톤 빈과 함께 사용시 문제점
- 스프링 컨테이너에 프로토 타입 빈을 요청하는 예제
- 클라이언트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 : 웹소켓과 동일한 생명주기를 가지는 스코프