스코프 : 빈이 존재할 수 있는 범위
스프링 빈은 기본적으로 싱글톤 스코프로 생성된다.
- 싱글톤
- 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- 프로토타입
- 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
- 웹 관련 스코프
- request : 웹 요청이 들어오고 나갈때까지 유지되는 스코프
- session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
- application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
@Scope("prototype")
@Component
public class HelloBean {}
@Scope("prototype")
@Bean
PrototypeBean HelloBean {
return new HelloBean();
}
스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
싱글톤 스코프는 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환한다.
- 싱글톤 스코프의 빈을 스프링 컨테이너에 요청한다.
- 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환한다.
- 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환한다.
public class SingletonTest {
@Test
void singletonBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
SingletonBean bean = ac.getBean(SingletonBean.class);
SingletonBean bean2 = ac.getBean(SingletonBean.class);
System.out.println("bean1 = " + bean);
System.out.println("bean2 = " + bean2);
assertThat(bean).isSameAs(bean2);
ac.close();
}
@Scope("singleton")
static class SingletonBean{
@PostConstruct
static void init(){
System.out.println("SingletonBean.init");
}
@PreDestroy
static void destroy(){
System.out.println("SingletonBean.destroy");
}
}
}
빈 초기화 메서드를 실행하고, 같은 인스턴스 빈을 조회하고 있으며, 종료 메서드까지 정상 호출된 것을 확인할 수 있다.
프로토타입 스코프는 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.
- 프로토타입 스코프 빈을 스프링 컨테이너에 요청한다.
- 스프링 컨테이너는 이 시점에 프로통타입 빈을 생성하고, 필요한 의존관계를 주입한다.
- 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환한다.
- 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.
💡 스프링 컨테이너느 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다. 클라이언트에 반환하면 끝! 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다.
💡 프로토타입을 관리하는 책임은 클라이언트에게!
💡 PreDestroy
같은 종료 메서드가 호출되지 않는다.
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("prototypeBean1 = " + prototypeBean1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
System.out.println("prototypeBean2 = " + prototypeBean2);
assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
}
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init(){
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy(){
System.out.println("PrototypeBean.destroy");
}
}
}
@PreDestroy
같은 빈의 종료 메서드가 실행되지 않는다.
- 스프링 컨테이너에 요청할 때마다 새로 생성된다.
- 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여한다.
- 빈의 종료 메서드가 호출되지 않는다.
- 프로토타입 빈을 조회한 클라이언트가 관리해야 한다.
- 따라서 종료 메서드에 대한 호출도 클라이언트가 직접 해야한다.
스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 새로운 객체 인스턴스를 생성해서 반환한다.
🚨 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의가 필요해요! 🚨
addCount()
를 호출addCount()
를 호출public class SingletonWithPrototypeTest1 {
@Test
void prototypeFind(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
assertThat(prototypeBean.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
은 의존관계 자동 주입 사용,clientBean
에 반환,clientBean
을 스프링 컨테이너에 요청해서 받음clientBean
이 반환clientBean.logic()
을 호출clientBean
은 prototypeBean의 addCount()
를 호출clientBean
을 스프링 컨테이너에 요청해서 받는데, 싱글톤이므로 같은 clientBean
이 반환clientBean.logic()
호출clientBean
은 prototypeBean의 addCount()
를 호출@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(2);
}
@Scope("singleton")
static class ClientBean{
private final PrototypeBean prototypeBean;
ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic(){
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
⭐ 우리의 바램은 프로토타입 빈을 주입 시점에만 새로 생성하는 것이 아니라, 사용할 때마다 새로 생성해서 사용하는 것!
참고
- 여러 빈에서 같은 프로토타입 빈을 주입 받으면,
- 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다.
- clientA, clientB가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입 빈을 주입 받는다.
clientA → prototypeBean@x01
clientB → prototypeBean@x02
사용할 때마다 새로 생성되는 것은 아님!
싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 어떻게 하면 사용할 때마다 항상 새로운 프로토타입 빈을 생성할 수 있을까?
싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새로 요청하는 것!
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");
}
}
}
ac.getBean()
을 통해 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있음@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;
}
}
prototypeBeanProvider.getObject()
을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.ObjectProvider
의 getObject()
를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다. (DL)ObjectProvider
는 지금 딱 필요한 DL 정도의 기능만 제공한다.
- ObjectFactory : 기능이 단순, 별도 라이브러리X, 스프링에 의존
- ObjectProvider : ObjectFactory 상속, 옵션, 스트림 처리 등 편의 기능이 많고, 별도 라이브러리 필요X, 스프링에 의존
javax.inject.Provider
라는 JSR-330 표준 사용javax.inject:javax.inject:1
라이브러리를 gradle에 추가package javax.inject;
public interface Provider<T> {
T.get();
}
@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;
}
}
provider.get()
을 통해서 새로운 프로토타입 빈이 생성되는 것을 확인provider
의 get()
을 호출하면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환(DL)Provider
는 지금 딱 필요한 DL 정도의 기능만 제공특징
get()
메서드 하나ㅗ 기능이 매우 단순- 별도의 라이브러리가 필요
- 자바 표준이므로 스프링이 아닌 다른 컨테이너에서 사용 가능
ObjectProvider
, JSR330 Provider
등은 프로토타입 뿐만 아니라 DL이 필요한 경우는 언제든지 사용할 수 있다.