0. 들어가며
최근에 스프링을 기초부터 공부하면서 저는 @Component나 @Service 같은 어노테이션을 사용하는 것은 익숙했지만 빈이 언제 생성되고 언제 소멸되는지, 즉 객체의 생명주기에 대해서는 전혀 모르고 있었다는 것을 깨달았습니다.
“이 객체가 얼마나 오래 살아야 하는가?”
“누가 이 객체를 관리할 책임이 있는가?”
를 명확히 아는 것이 매우 중요합니다. 이를 관리해주는 것이 바로 스코프(Scope) 개념입니다.
스코프를 이해하면 IOC 컨테이너가 빈을 어떻게 관리하는지 그리고 싱글톤과 프로토타입, 웹 스코프가 왜 존재하는지 자연스럽게 이해할 수 있습니다.
이번 글에서는 제가 공부하면서 정리한 내용을 바탕으로
- 스프링에서 지원하는 대표적인 스코프
- 각 스코프가 IOC 컨테이너와 어떻게 연결되어 관리되는지
를 중심으로 쉽게 풀어서 설명드리려고 합니다.
이 글이 스프링의 스코프를 이해하고, 실제 개발에서 설계할 때 바로 활용하는 데 도움이 되었으면 좋겠습니다.
1. 스코프(Scope)란 무엇인가?
스코프는 간단히 말해, 빈이 존재할 수 있는 범위를 의미합니다. 조금 더 구체적으로 표현하면
“스프링 IOC 컨테이너가 이 빈을 언제 생성하고, 언제까지 관리할 것인가?”
를 정의하는 개념이라고 할 수 있습니다.
즉 스코프를 이해한다는 것은 단순히 객체가 얼마나 오래 살아 있는지를 아는 것이 아니라 IOC 컨테이너의 관리 책임 범위를 이해하는 것과 같습니다.
2. 스프링이 지원하는 주요 스코프
2-1. 싱글톤 스코프
싱글톤 스코프는 스프링의 기본 스코프입니다.
- 스프링 컨테이너가 시작될 때 빈을 생성하며, 컨테이너 종료까지 유지됩니다.
- 컨테이너 안에서 단 하나의 인스턴스만 존재합니다.
즉, 싱글톤 빈은 컨테이너와 생명주기가 동일하며, 대부분의 서비스나 레포지토리 계층에서 적합합니다.
싱글톤 빈 요청 과정
- 클라이언트가 싱글톤 빈을 요청합니다.
- 스프링 컨테이너는 이미 생성되어 있는 빈을 반환합니다.
- 이후 동일한 요청이 들어와도 항상 같은 객체 인스턴스를 반환합니다.
싱글톤 스코프 덕분에 메모리를 효율적으로 사용할 수 있으며, 여러 요청에서 공유가 필요한 상태를 안전하게 관리할 수 있습니다.
2-2. 프로토타입 스코프
프로토타입 스코프는 싱글톤 스코프와 반대되는 특징을 갖고 있습니다.
- 빈을 요청할 때마다 새로운 인스턴스를 생성합니다.
- 스프링 컨테이너는 생성과 의존관계 주입까지만 관여하며 이후에는 빈을 더 이상 관리하지 않습니다.
프로토타입 빈 요청 과정
- 클라이언트가 프로토타입 빈을 요청합니다.
- 스프링 컨테이너는 새로운 빈을 생성하고 필요한 의존관계를 주입합니다.
- 생성된 빈을 클라이언트에게 반환합니다.
- 이후 컨테이너는 해당 빈을 관리하지 않습니다.
- 동일한 빈을 다시 요청하면, 항상 새로운 인스턴스를 반환합니다.
즉, 프로토타입 빈은 컨테이너에서 생성과 초기화까지만 책임지고 그 이후 생명주기는 클라이언트 책임입니다.
때문에 @PreDestroy 같은 종료 메서드는 자동으로 호출되지 않습니다.싱글톤 빈 내부에서 프로토타입 빈을 주입받으면, 프로토타입 빈이 의도한 대로 매번 새로 생성되지 않고, 싱글톤처럼 동작할 수 있습니다.
이런 상황에서는 ObjectProvider나 Provider를 사용해 요청 시점마다 새로운 프로토타입 빈을 생성하도록 설계해야 합니다.
프로토타입 스코프 사용 시 주의점
싱글톤 빈 내부에서 프로토타입 빈을 주입받으면, 프로토타입 빈이 의도한 대로 매번 새로 생성되지 않고, 싱글톤처럼 동작할 수 있습니다.
이런 상황에서는 ObjectProvider나 Provider를 사용해 요청 시점마다 새로운 프로토타입 빈을 생성하도록 설계해야 합니다.
예시상황
- 회원 포인트를 관리하는 SingletonService 싱글톤 빈이 있습니다.
- 회원 포인트를 개별 요청마다 계산하기 위해, PrototypeCounter라는 프로토타입 빈을 사용하도록 설계했습니다.
- 그런데 SingletonService 생성 시점에 PrototypeCounter를 주입받으면, 싱글톤 빈이 생성되는 시점에 한 번만 프로토타입 빈이 생성됩니다.
실제 코드 흐름은 이렇게 됩니다:
@Component
@Scope("singleton")
public class SingletonService {
private final PrototypeCounter counter;
public SingletonService(PrototypeCounter counter) {
this.counter = counter;
}
public int addCount() {
return counter.addCount();
}
}
@Component
@Scope("prototype")
public class PrototypeCounter {
private int count = 0;
public int addCount() {
count++;
return count;
}
}
문제 상황
- 클라이언트 A가 SingletonService.addCount()를 호출 → count = 1
- 클라이언트 B가 SingletonService.addCount()를 호출 → count = 2
즉, 프로토타입 빈인데도 싱글톤처럼 공유되는 현상이 발생합니다.
결과적으로 각 요청마다 새 객체를 생성해야 하는 프로토타입 빈의 의미가 사라지게 됩니다.
해결 방법
이 문제를 해결하기 위해 스프링에서는 ObjectProvider나 Provider를 사용합니다.
이렇게 하면 싱글톤 빈 내부에서 프로토타입 빈을 요청할 때마다 새로운 인스턴스를 생성할 수 있습니다.
@Component
@Scope("singleton")
public class SingletonService {
private final ObjectProvider<PrototypeCounter> counterProvider;
public SingletonService(ObjectProvider<PrototypeCounter> counterProvider) {
this.counterProvider = counterProvider;
}
public int addCount() {
PrototypeCounter counter = counterProvider.getObject(); // 매번 새로 생성
return counter.addCount();
}
}
이제 클라이언트 A, B가 호출할 때마다 각자 독립적인 PrototypeCounter 인스턴스가 생성되어 올바르게 동작합니다.
2.3 웹 스코프
웹 스코프는 웹 환경에서만 동작하는 스코프입니다.
- 프로토타입과 달리, 스프링이 종료 시점까지 관리합니다.
- 종료 시점에는 @PreDestroy 같은 메서드도 정상 호출됩니다.
웹 스코프에는 여러 종류가 있지만 여기서는 request 스코프를 중심으로 설명드립니다.
Request 스코프
- HTTP 요청 하나가 들어오고 나갈 때까지 유지됩니다.
- 각 요청마다 별도의 빈 인스턴스를 생성하며 요청 종료 시점에 소멸합니다.
즉, 하나의 HTTP 요청마다 하나의 빈이 생성되어 관리되는 구조입니다.
이 덕분에 요청 단위로 상태를 안전하게 보관할 수 있고, 로그 추적이나 요청별 데이터 관리에 적합합니다.
3. IOC 컨테이너 관점에서 스코프 정리
스코프생성 시점관리 범위종료 관리
| 싱글톤 | 컨테이너 시작 시 | 컨테이너 종료까지 | O |
| 프로토타입 | 빈 요청 시 | 생성 + 의존관계 주입까지만 | X |
| request | HTTP 요청 시 | 요청 종료 시점까지 | O |
스코프는 “객체의 수명”이 아니라, “IOC 컨테이너가 어디까지 책임질 것인가”를 정의하는 개념입니다.
이를 이해하면 왜 싱글톤이 기본이고 프로토타입은 종료 관리를 하지 않으며, 웹 스코프가 별도로 존재하는지 자연스럽게 이해할 수 있습니다.
마치며
이번 글을 정리하면서 개인적으로 느낀 점은 스프링의 스코프 개념이 단순히 “빈의 생명주기”를 설명하는 것에 그치지 않는다는 것입니다.
처음에는 싱글톤, 프로토타입, request 같은 용어가 그냥 “규칙”처럼 느껴졌습니다. 하지만 직접 코드와 흐름을 따라가면서
“어떤 객체를 누가, 언제, 어디까지 책임지고 관리하는지”를 명확히 구분하는 것이 스프링 설계의 핵심이라는 사실를 깨달았습니다.
특히 프로토타입 스코프와 싱글톤 스코프를 함께 사용할 때 발생할 수 있는 문제를 생각하면서 단순히 새로운 객체를 생성하는 것만으로는 충분하지 않고, 컨테이너와 클라이언트의 책임을 정확히 이해하고 설계해야 한다는 점이 크게 와닿았습니다.
'스프링' 카테고리의 다른 글
| FIRST 원칙으로 바라본 테스트 코드 작성법 (0) | 2026.01.30 |
|---|---|
| Spring Validation 완전 정복 - 올바른 유효성 검증 전략 (0) | 2026.01.29 |
| 스프링을 스프링답게 만드는 진짜 이유: IoC·AOP·PSA 삼각형의 정체 (0) | 2026.01.18 |
| 스프링 배치란? (0) | 2025.12.11 |
| @Data 사용을 지양 해야 하는 이유 (0) | 2025.10.22 |