빈 스코프란?
- 빈 스코프란, 번역 그대로 빈이 존재할 수 있는 범위를 뜻한다.
- 스프링이 지원하는 스코프
- 싱글톤: 기본 스코프이며, 스프링 컨테이너의 시작과 종료까지 유지된다. 가장 넓은 범위
- 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고, 더는 관리하지 않는 짧은 범위의 스코프
- 웹 관련
- request: 웹 요청이 들어오고 나갈때까지 유지
- session: 웹 세션이 생성되고 종료될때까지 유지
- application: 웹 서블릿 컨텍스트와 같은 범위로 유지
프로토타입 스코프
싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환한다. 반면, 스코프가 프로토타입인 빈을 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.
클라이언트가 프로토타입 스코프인 빈을 스프링 컨테이너에 요청하면, 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다. 스프링 컨테이너는 생성한 프로토 타입 빈을 클라이언트에 반환하며, 관리하지 않는다. 이후 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.
핵심
스프링 컨테이너는 프로토 타입 빈을 생성하고, 의존관계 주입, 초기화 까지만 처리한다.
스프링 컨테이너가 프로토 타입 빈을 관리하지 않으므로, 프로토타입 빈을 관리할 책임은 클라이언트에 있다. 따라서 @PreDestroy
같은 종료 메서드가 호출되지 않는다.
프로토타입 스코프를 싱글톤 빈과 함께 사용 시 문제점
싱글톤 빈은 위에서 설명한 바와 같이 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 또한 주입된다.
1. clientBean은 생성 시 의존관계 자동 주입을 통해 스프링 컨테이너에 프로토타입 빈을 요청한다
2. 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환한다.
3. clientBean은 프로토타입 빈을 (정확히는 참조값) 보관한다.
프로토타입 빈이 addCount() 라는 메서드를 가지고 있다고 해보자. addCount()의 기능은 count의 값을 1 증가시키는 것이다. 각각의 클라이언트들이 clientBean.logic() 을 통해 프로토타입 빈의 addCount()를 호출할 때, 어떤 결과가 나올까?
클라이언트는 clientBean이 프로토타입 빈을 가지고 있고, 각각의 요청 마다 새로운 프로토타입 빈을 생성하여 addCount() 로직을 수행할 것으로 예상할 것이다. 즉, 클라이언트 A가 logic을 한 번 호출하면 clientBean이 새로 생성된 프로토타입 빈에 addCount()를 수행할 것이므로 count는 1일 것으로 예상한다. 또한, 클라이언트 B도 logic을 호출한다면 count는 1일 것으로 예상할 것이다.
그러나, 클라이언트 B 가 logic()을 호출하고, clientBean이 프로토타입 빈의 addCount()를 호출했을 때, 결과값 count는 2이다. clientBean이 싱글톤 타입이기 때문이다.
위의 예시에서 보면 알 수 있듯이, clientBean이 내부에 가지고 있는 프로토타입 빈은 과거에 주입이 끝난 빈이다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이지, 사용 할 때마다 새로 생성되는 것이 아니다!
문제 해결 - Provider
싱글톤과 프로토 타입 빈을 함께 사용할 때, 요청을 받을 때마다 항상 새로운 프로토타입 빈을 생성하려면, Provider 를 사용하면 된다. 싱글톤 빈이 프로토타입 빈을 사용할 때마다 스프링 컨테이너에 새로 요청하는 방법도 있는데, 스프링 ApplicationContext를 주입받게 되므로 스프링 컨테이너에 종속적인 코드가 되고, 단위테스트도 어려워지므로 사용하지 않는다.
ObjectFactory, ObjectProvider
지정한 빈을 컨테이너에서 대신 찾아주는 Dependency Lookup 서비스를 제공하는 것이 ObjectProvider 이다. 과거 ObjectFactory에 편의 기능을 추가해서 만든 것이 ObjectProvider이다.
코드
@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)
- 스프링이 제공하는 기능을 사용하여 스프링 종속적이다
- 그러나, 기능이 단순하므로 단위테스트나 mock 코드를 만들기 쉬워진다.
ObjectFactory: 기능이 단순하며 별도의 라이브러리가 필요없다. 스프링에 의존적
ObjectProvider: ObjectFactory 상속, 옵션, 스트림처리 등 편의 기능이 많다. 별도의 라이브러리가 필요없음,스프링 의존적.
JSR-330 Provider
JSR-330이라는 자바 표준을 사용하는 방법이다. 먼저 gradle에 아래 라이브러리를 추가한다
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;
}
- provider.get()을 통해 항상 새로운 프로토타입 빈이 생성된다
- provider.get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아 반환한다(DL)
- 자바 표준이고, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기 훨씬 쉽다
- Provider는 get() 메서드 하나로 기능이 매우 단순하며, 별도의 라이브러리가 필요하다.
- 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
정리
- 싱글톤 빈에서 프로토타입 빈을 사용할 때에는 문제가 발생할 수 있다.
- 이를 해결하기 위해선 Provider를 사용해야 한다.
- 사실 실무에서는 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 매우 드물다.
- `ObjectProvider`, `JSR330 Provider` 등은 프로토타입 뿐만 아니라 DL이 필요한 경우 언제든지 사용할 수 있다.
'Spring' 카테고리의 다른 글
빈 생명주기 콜백 (1) | 2025.02.04 |
---|---|
싱글톤 패턴과 싱글톤 컨테이너(스프링 컨테이너) (0) | 2024.12.13 |
스프링 컨테이너와 스프링 빈(ApplicationContext, BeanFactory, BeanDefinition) (1) | 2024.11.27 |
객체 지향 설계와 SOLID 원칙, 스프링 (0) | 2024.10.28 |
Spring Bean, DI, Component Scan (0) | 2024.09.25 |