clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가한다. 원래 1이였으므로 2가 된다.
Test
public class SingletonWithPrototypeTest1 {
@Test
void singletonClientUsePrototype(){
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(ClientBean.class , PrototypeBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
Assertions.assertThat(count1).isEqualTo(1);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
Assertions.assertThat(count2).isEqualTo(2);
}
@Scope("singleton")
static class ClientBean{
private final PrototypeBean prototypeBean;
// PrototypeBean 호출 시점이 ClientBean이 스프링 빈으로 등록 될 때 1번 만 호출
// 결국 같은 PrototypeBean이 사용된다.
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic(){
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@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 destory(){
System.out.println("PrototypeBean.destroy");
}
}
}
스프링은 일반적으로 싱글톤 빈을 사용하므로 , 싱글톤 빈이 프로토타입 빈을 사용하게 된다.
그런데 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에 , 프로토타입 빈이 생성되기는 하지만 싱글톤 빈과 함께 계속 유지되는 것이 문제다.
프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라 , 사용할 때 마다 새로 생성해서 사용하는것을 원한다.
참고
여러 빈에서 같은 프로토타입 빈을 주입 받으면 , "주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다"
예를 들어
clientA , clientB가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입 빈을 주입 받는다.
clientA -> prototypeBean@x01
clientB -> prototypeBean@x02
물론 사용할 때 마다 새로 생성되는 것은 아니다.
문제 해결
싱글톤 빈과 프로토타입 빈을 함께 사용할 때 ,
어떻게 하면 사용할 때 마다 항상 새로운 프로토타입 빈을 생성할 수 있을까?
스프링 컨테이너에 요청
가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청하는 것이다.
ac.getBean()을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
의존관계를 외부에서 주입(DI)받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL)의존관계 조회(탐색) 이라 한다.
그런데 이렇게 스프링의 애플리케이션 컨텍스트 전체를 주입 받게 되면 , 스프링 컨테이너에 종속적인 코드가 되고 ,단위 테스트도 어려워 진다.
지금 필요한 기능은 지정한 프로토타입 빈을 컨테이너 에서 대신 찾아주는 DL정도의 기능만 제공하는 무언가가 있으면 된다.
ObjectProvider , ObjectFactory
@Scope("singleton")
static class ClientBean{
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@Scope("singleton")
static class ClientBean{
@Autowired
private ObjectFactory<PrototypeBean> prototypeBeanProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
ObjectProvider
prototypeBeanProvider.getObject()는 ac.getBean() 처럼 스프링 컨테이너를 통해 (제네릭에 정의된)PrototypeBean을 찾아 반환해준다. (DL)
스프링이 제공하는 기능을 사용하지만 , 기능이 단순하므로 단위테스트를 만들거나 mock코드를 만들기는 훨씬 쉬워진다.
너무 무거운 ac를 새로 생성하는 것 보다 딱 필요한 DL정도의 기능만 제공한다.
ObjectFactory 상속 , 옵션 , 스트림 처리의 편의 기능이 많고, 별도의 라이브러리 필요없고, 스프링에 의존한다.
ObjectFactory
기능이 단순 , 별도의 라이브러리 필요 없고 , 스프링에 의존한다.
getObject() 메소드 하나만 존재한다.
JSR-330 Provider
마지막 방법은 javax.inject.Provider라는 JSR-330 자바 표준을 사용하는 방법이다.
Gradle추가
implementation 'javax.inject:javax.inject:1'
@Scope("singleton")
static class ClientBean{
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
provider.get()을 통해 항상 새로운 프로토타입 빈을 생성한다.
provider.get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(DL)
기능이 단순하므로 단위테스트를 만들거나 mock코드를 만들기는 훨씬 쉬워진다.
Provider는 지금 딱 필요한 DL정도의 기능만 제공한다.
자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
그러면 프로토타입 빈을 언제 사용할까?
매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 된다.
그런데 실무에서 개발하다 보면 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접 적으로 사용하는 경우는 매우 드물다.
ObjectProvider는 DL을 위한 편의 기능을 많이 제공해주고 , 스프링 외에 별도의 의존관계 추가가 필요 없이 때문에 편리하다.
만약 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면 JSR-330 Provider를 사용해야 한다.
싱글톤 빈과 함께 사용시 문제점 과 문제 해결 방법
스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다.
하지만 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의해야 한다.
프로토타입 빈 직접 요청
Test
싱글톤 빈에서 프로토타입 빈 사용
이번에는 clientBean이라는 싱글톤 빈이 의존관계 주입을 통해서 프로토타입 빈을 주입받아서 사용하는 예를 보자
Test
참고
문제 해결
싱글톤 빈과 프로토타입 빈을 함께 사용할 때 ,
어떻게 하면 사용할 때 마다 항상 새로운 프로토타입 빈을 생성할 수 있을까?
스프링 컨테이너에 요청
가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청하는 것이다.
ObjectProvider , ObjectFactory
JSR-330 Provider
마지막 방법은 javax.inject.Provider라는 JSR-330 자바 표준을 사용하는 방법이다.
Gradle추가
그러면 프로토타입 빈을 언제 사용할까?
스프링 핵심 원리 - 기본편 - 인프런
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 초급 프레임워크 및 라이브러리 웹 개발 서버 개발 Back-End Spring 객체지향 온
www.inflearn.com
'스프링 핵심 원리 > 프로토 타입 스코프' 카테고리의 다른 글