💋 빈 스코프란?
빈 스코프는 빈이 존재할 수 있는 범위를 뜻한다.
스프링에서 기본으로 제공하는 빈은 싱글톤 빈이다!
싱글톤 빈은 스프링 컨테이너 생성 시에 생성되고, 애플리케이션 종료(스프링 컨테이너 종료)까지 스프링 컨테이너를 통해 관리된다.
별도의 설정을 통해서, 빈이 존재하는 범위를 변경할 수 있다.
💋 빈 스코프의 종류
스프링에서 제공하는 빈 스코프 종류는 아래와 같다.
1. 싱글톤 스코프
- 기본 스코프
- 스프링 컨테이너 생성 시 함께 생성되고, 애플리케이션 종료까지 유지되는 가장 넓은 범위의 스코프
- 가장 일반적으로 사용 (거의 대부분의 경우에 사용한다고 보면 된다!)
2. 프로토타입 스코프
- 빈의 생성과 의존관계 주입까지만 하고, 더는 스프링 컨테이너를 통해 관리하지 않는 경우에 프로토타입 스코프를 지정하면 된다.
- 매우 짧은 범위의 스코프
3. 웹 관련 스코프
이외에도 request, session, application과 관련된 스코프도 존재한다.
request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
✔ 싱글톤 스코프
기본적으로 싱글톤 스코프로 등록되기는 하지만 위에 어노테이션과 함께 스코프를 지정해봤다.
@Scope("singleton")
static class SingletonBean {
@PostConstruct
public void init() {
System.out.println("SingletonBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("SingletonBean.destroy");
}
}
아래와 같이 테스트를 작성하면,
@Test
void singletonBeanFind() {
final AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
final SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
final SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
System.out.println("singletonBean1 = " + singletonBean1);
System.out.println("singletonBean2 = " + singletonBean2);
assertThat(singletonBean1).isSameAs(singletonBean2);
ac.close();
}
ApplicationContext 생성과 함께 빈의 @PostConstruct init 메서드가 실행되고,
ApplicationContext에 bean 요청을 두 번 하고 있지만 동일한 객체를 스프링 컨테이너를 통해 반환받고 있는 것을 확인할 수 있다. (테스트는 통과함)
그리고 ApplicationContext를 종료하는 close()를 호출한 경우에, 싱글톤 빈도 함께 @PreDestroy destroy() 종료 메서드를 호출하는 것을 확인할 수 있다.
✔ 프로토타입 스코프
이번에는 스코프를 프로토타입으로 지정해봤다.
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
아래 테스트를 실행해보면,
@Test
void prototypeBeanFind() {
final AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
System.out.println("[[[ prototype bean 1 ]]]");
final PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
System.out.println("[[[ prototype bean 2 ]]]");
final PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
System.out.println("prototypeBean1 = " + prototypeBean1);
System.out.println("prototypeBean2 = " + prototypeBean2);
assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
ac.close();
}
아래와 같은 결과가 출력되는데, 유의미한 특징을 아래에서 살펴볼 예정이다.
1. ApplicationContext 생성 시점이 아니라, 각 빈을 호출하는 시점에 @PostConstruct init() 메서드가 실행된다.
2. 각 빈이 서로 다른 주소를 참조하고 있다. (다른 객체임)
따라서 isNotSameAs()로 작성한 테스트 코드도 통과한다.
3. ApplicationContext가 종료되면서, 스프링 컨테이너가 소멸하지만, 빈은 소멸되지 않아 @PreDestroy destroy() 메서드는 호출되지 않는다.
프로토타입 빈을 소멸시키려면, 직접 destroy() 메서드를 실행해 주어야 한다. ㅎㅎㅎㅎㅎ (수작업)
중요하게 생각해볼 것은, 스프링 컨테이너에 직접 빈을 호출하는 경우에만 새로운 빈을 생성해서 반환한다는 것이다.
싱글톤 빈에서 프로토타입 빈을 주입받도록 설정하게 되면, 프로토타입 빈도 싱글톤 빈과 함께 관리되기 때문에 주의해서 사용해야 한다.
이 부분을 해결하기 위해서는 Provider를 공부해보면 좋을 것 같다!