스프링 빈 스코프는 빈의 생성과 소멸에 관한 범위를 정의하는 방법입니다. 즉, 스코프는 번역 그대로 빈이 존재할 수 있는 범위를 뜻합니다.
프링은 다양한 빈 스코프를 제공합니다. 각 스코프는 특정한 상황에서 빈 인스턴스의 생성과 소멸을 처리하도록 설계되었습니다. 가장 기본적인 스코프는 싱글톤(Singleton)으로, 스프링 컨테이너 내에서 한 개의 빈 인스턴스만 생성되고 이를 모든 빈에서 공유합니다.
싱글톤을 포함하여 다음과 같은 스코프가 있습니다.
Singleton: 애플리케이션 전체에서 하나의 빈 인스턴스를 생성하고 공유합니다. 이는 스프링의 기본 스코프이며, 따로 설정하지 않으면 모든 빈은 이 스코프로 생성됩니다.
Prototype: 빈 요청마다 새로운 인스턴스를 생성합니다. 매번 새로운 객체를 생성하므로 상태를 유지하지 않습니다.
Request: 웹 애플리케이션에서 HTTP 요청 당 하나의 빈 인스턴스를 생성합니다. 각각의 HTTP 요청이 처리될 때마다 새로운 인스턴스를 생성하고 이를 요청 스코프에 저장합니다.
Session: 웹 애플리케이션에서 HTTP 세션 당 하나의 빈 인스턴스를 생성합니다. HTTP 세션이 생성될 때마다 새로운 인스턴스를 생성하고 이를 세션 스코프에 저장합니다.
Global session: Portlet 기반 웹 애플리케이션에서 Portlet 세션 당 하나의 빈 인스턴스를 생성합니다.
Application: ServletContext 당 하나의 빈 인스턴스를 생성합니다. 웹 애플리케이션이 시작될 때 스프링 컨테이너가 생성되며, 이 때 하나의 인스턴스를 생성하고 이를 애플리케이션 스코프에 저장합니다.
Websocket: WebSocket 당 하나의 빈 인스턴스를 생성합니다. 각각의 WebSocket 세션이 시작될 때마다 새로운 인스턴스를 생성하고 이를 WebSocket 스코프에 저장합니다.
싱글톤 스코프(singleton scope)
프로토타입 스코프(prototype scope)
참고!! 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계를 주입, 초기화까지만 담당한다. 즉! 생성된 프로토타입 빈을 클라이언트에게 반환한 후엔 스프링 컨테이너는 생성되었던 프르토타입 빈을 관리하지 않는다. 그래서 @PreDestroy같은 소멸 메소드가 호출되지 않는다.
싱글톤 스코프 빈 요청 테스트코드 예시
public class SingletonTest {
@Test
public void singletonBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
System.out.println("singletonBean1 = " + singletonBean1);
System.out.println("singletonBean2 = " + singletonBean2);
Assertions.assertThat(singletonBean1).isEqualTo(singletonBean2);
ac.close();
}
@Scope("singleton")
static class SingletonBean {
@PostConstruct
public void init() {
System.out.println("SingletonBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("SingletonBean.destroy");
}
}
}
출력 결과
프로토타입 스코프 빈 요청 테스트코드 예시
public class PrototypeBeanFind {
@Test
public void prototypeBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean PrototypeBean1 = ac.getBean(PrototypeBean.class);
PrototypeBean PrototypeBean2 = ac.getBean(PrototypeBean.class);
System.out.println("singletonBean1 = " + PrototypeBean1);
System.out.println("singletonBean2 = " + PrototypeBean2);
Assertions.assertThat(PrototypeBean1).isNotSameAs(PrototypeBean2);
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");
}
}
}
출력 결과
위에서 얘기했듯이 프로토타입 스코프 빈을 요청하면 항상 새로운 객체 인스턴스를 생성하고 반환한다고 얘기했습니다.
하지만 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의해야한다.
먼저 스프링 컨테이너에 프로토타입 빈을 직접!! 요청하는 예시코드를 보여드리겠습니다.
public class SingletonWithPrototypeTest1 {
@Test
void prototypeFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
Assertions.assertThat(prototypeBean1.getCount()).isEqualTo(1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
Assertions.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이라는 싱글톤 빈이 의존관계 주입을 통해 프로토타입 빈을 주입받아 사용하는 예시코드를 보여드리겠습니다.
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 PrototypeBean prototypeBean; // 생성시점에 주입
@Autowired // 생성자가 1개이면 @Autowired 생략가능
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
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");
}
}
}
순서!!!
클라이언트 A는 ClientBean을 스프링 컨테이너에게 요청해서 받는다. ClientBean은 싱글톤이기에 항상 같은 인스턴스를 반환된다.
클라이언트 B도 클라이언트 A와 마찬가지가 같은 싱글톤 빈을 반환된다.
중요한 점은 싱글톤 빈인 clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거(빈의 생성시점)에 주입이 끝난 빈이다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이므로 사용 할 때마다 새로 생성되는 것이 아니다.!!!
싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 사용할 때 마다 항상 새로운 프로토타입 빈을 생성할 수 있는 방법을 말씀드리겠습니다.
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(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");
}
}
}
실행하면 prototypeBeanProvider.getObject()을 통해 항상 새로운 프로토타입 빈을 생성되는 것을 확인할 수 있으며 또한 각각의 count가 1인 것을 확인할 수 있다.
Provider인터페이스를 사용하기 위해서는 라이브러리를 추가해야 한다.
스프링부트 3.0미만
javax.inject:javax.inject:1
스프링부트 3.0이상
jakarta.inject:jakarta.inject-api:2.0.1
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}