스프링 빈 스코프(Bean Scope)
한눈에 보기
빈 스코프는 스프링 컨테이너가 빈을 언제 생성하고 어디까지 공유할지 정하는 규칙이다. 싱글턴과 프로토타입의 동작 차이, 웹 스코프, 스코프 혼용 시 문제와 ObjectProvider, 프록시 해결법을 정리한다.
최종 업데이트 2026.09.17
스프링 빈 스코프(Bean Scope)는 스프링 컨테이너가 빈(Bean) 인스턴스를 언제 생성하고 얼마나 오래 유지하며 어느 범위까지 공유할지를 정하는 규칙이다. 기본값인 싱글턴(Singleton) 스코프는 컨테이너당 인스턴스 하나를 모든 곳에서 공유하고, 프로토타입(Prototype) 스코프는 요청할 때마다 새 인스턴스를 만들며, 웹 환경에서는 request와 session 같은 웹 스코프를 추가로 쓸 수 있다.
스코프가 결정하는 것
스프링 컨테이너는 빈 정의(Bean Definition)를 읽어 객체를 생성하고 의존성을 주입하며 생명주기를 관리한다. 이때 스코프는 두 가지를 결정한다. 첫째, 같은 빈을 여러 곳에서 주입받을 때 같은 인스턴스를 줄 것인지 새 인스턴스를 줄 것인지다. 둘째, 컨테이너가 그 빈의 소멸까지 책임질 것인지다. 스코프는 @Scope 애너테이션(Annotation)으로 지정하며, 지정하지 않으면 싱글턴이다.
스코프 종류
| 스코프 | 인스턴스 범위 | 비고 |
|---|---|---|
| singleton | 스프링 컨테이너당 1개 | 기본값, 컨테이너가 소멸까지 관리 |
| prototype | 요청할 때마다 새로 생성 | 생성과 주입까지만 관여, 소멸 콜백 미호출 |
| request | HTTP 요청당 1개 | 웹 환경 전용 |
| session | HTTP 세션당 1개 | 웹 환경 전용 |
| application | 서블릿 컨텍스트당 1개 | 웹 환경 전용 |
| websocket | 웹소켓 세션당 1개 | 웹소켓 환경 전용 |
싱글턴 스코프의 내부 동작
싱글턴 빈은 컨테이너가 refresh되는 시점에 미리 생성되어 내부 싱글턴 저장소에 이름과 함께 보관된다. 이후 어디에서 주입받든 같은 인스턴스를 돌려받는다. 디자인 패턴의 싱글턴 패턴과 달리 클래스 자체에 private 생성자나 static 인스턴스를 두지 않고, 컨테이너가 하나만 만들어 공유한다는 점이 다르다. 이를 싱글턴 레지스트리라고 부르며, 덕분에 테스트에서 인스턴스를 자유롭게 만들 수 있다.
싱글턴 빈은 여러 스레드가 동시에 사용하므로 반드시 무상태(Stateless)로 설계해야 한다. 요청마다 달라지는 값을 필드에 보관하면 스레드 간에 값이 섞이는 동시성 문제가 생긴다. 상태가 필요하면 지역 변수, 메서드 파라미터, 반환 값으로 다뤄야 한다.
@Component
public class OrderService {
// 위험: 싱글턴 빈의 공유 필드에 요청별 상태를 저장하면 안 된다
// private int lastOrderAmount;
public int order(int amount) {
int fee = calculateFee(amount); // 지역 변수로 처리한다
return amount + fee;
}
private int calculateFee(int amount) {
return amount / 100;
}
}
프로토타입 스코프와 생명주기 콜백
프로토타입 빈은 컨테이너에 요청하는 순간 생성되고, 의존성 주입과 @PostConstruct 초기화까지 마친 뒤 클라이언트에게 넘겨진다. 그 이후 컨테이너는 이 인스턴스를 더 이상 추적하지 않는다. 따라서 @PreDestroy 같은 소멸 콜백이 호출되지 않으며, 자원 해제가 필요하면 사용하는 쪽이 직접 정리해야 한다.
@Component
@Scope("prototype")
public class ReportGenerator {
@PostConstruct
public void init() {
// 요청마다 새 인스턴스에서 호출된다
}
@PreDestroy
public void close() {
// 프로토타입이므로 컨테이너가 호출해 주지 않는다
}
}
스코프가 다른 빈을 섞을 때 생기는 문제
가장 자주 나오는 함정은 싱글턴 빈이 프로토타입 빈을 주입받는 경우다. 주입은 싱글턴이 생성되는 시점에 한 번만 일어나므로, 프로토타입 빈도 그 순간 만들어진 인스턴스 하나가 계속 재사용된다. 매번 새 인스턴스를 원한 의도와 어긋나는 것이다. 해결책은 사용 시점에 컨테이너에 조회를 위임하는 것이며, ObjectProvider가 대표적이다.
@Component
public class ReportService {
private final ObjectProvider<ReportGenerator> generatorProvider;
public ReportService(ObjectProvider<ReportGenerator> generatorProvider) {
this.generatorProvider = generatorProvider;
}
public void generate() {
ReportGenerator generator = generatorProvider.getObject(); // 호출 시점마다 새 인스턴스
// ...
}
}
request 같은 웹 스코프 빈을 싱글턴에 주입할 때는 프록시(Proxy) 방식을 쓴다. @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)로 선언하면 CGLIB 프록시가 대신 주입되고, 실제 메서드 호출 시점에 현재 요청에 해당하는 진짜 빈을 찾아 위임한다. 요청이 없는 시점에 웹 스코프 빈을 직접 주입하려 하면 컨텍스트 기동 자체가 실패하므로, 프록시나 ObjectProvider로 조회를 지연시키는 것이 핵심이다.
면접에서 이렇게 물어본다
- 스프링 빈의 기본 스코프는 무엇이고, 싱글턴 빈을 무상태로 설계해야 하는 이유를 설명해 보라.
- 스프링의 싱글턴 레지스트리와 디자인 패턴의 싱글턴 패턴은 무엇이 다른지 말해 보라.
- 프로토타입 빈은 왜 @PreDestroy가 호출되지 않는지 컨테이너의 관리 범위와 연결해 설명해 보라.
- 싱글턴 빈에 프로토타입 빈을 주입하면 어떤 문제가 생기고 어떻게 해결하는지 말해 보라.
- request 스코프 빈을 싱글턴 빈에서 사용할 때 프록시 모드가 필요한 이유를 설명해 보라.
Spring / JPA에서 같이 읽기
- JPA(Java Persistence API)JPA는 자바 객체와 관계형 데이터베이스 테이블의 매핑을 표준으로 정의한 ORM 명세다. 명세와 구현체의 관계, 영속성 컨텍스트의 기능, 엔티티 생명주기, N+1 문제까지 면접 관점에서 정리한다.
- 더티 체킹(Dirty Checking)더티 체킹은 영속성 컨텍스트가 엔티티 변경을 감지해 플러시 시점에 UPDATE 쿼리를 자동 생성하는 JPA 기능이다. 스냅샷 비교 원리, 플러시 시점, 준영속 엔티티와 벌크 연산의 주의점을 다룬다.
- SpringApplication과 부트스트랩 과정SpringApplication은 스프링 부트의 main 메서드에서 부트스트랩을 총괄하는 클래스다. run() 내부의 환경 준비, 컨텍스트 생성, refresh, 러너 실행 순서와 자동 구성 원리를 설명한다.
- Spring Boot 테스트 코드 작성Spring Boot 테스트는 컨테이너를 어디까지 띄우느냐에 따라 통합 테스트와 슬라이스 테스트로 나뉜다. @SpringBootTest, @WebMvcTest, @DataJpaTest 활용법과 컨텍스트 캐싱 주의점을 다룬다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기