Spring
Spring Bean Scope에 대해서 설명해주세요
Spring Bean의 생명주기 범위(singleton, prototype, request, session)를 완벽히 이해하고, 스코프 간 주입 문제와 해결 방법을 배웁니다.
2026년 3월 21일 · 약 12분 읽기
Q. "Spring Bean Scope에 대해서 설명해주세요. singleton과 prototype의 차이점과, singleton Bean에 prototype Bean을 주입할 때 발생하는 문제를 말씀해주세요."
예상 꼬리질문
답변 가이드
"Bean Scope는 Spring IoC 컨테이너가 Bean 인스턴스를 생성하고 관리하는 범위를 정의합니다. singleton은 컨테이너당 한 개의 인스턴스만 생성하여 모든 곳에서 공유하며, prototype은 요청할 때마다 새 인스턴스를 생성합니다. 그 외에도 웹 환경에서 사용하는 request(HTTP 요청마다), session(세션마다), application(ServletContext마다) 스코프가 있습니다."
"Singleton의 핵심은 인스턴스 재사용입니다. 컨테이너 시작 시 한 번만 생성되어 메모리와 성능 측면에서 효율적이지만, 상태(state)를 가지면 안 됩니다. 여러 스레드가 같은 인스턴스에 접근하므로 필드에 사용자별 데이터를 저장하면 Race Condition이 발생합니다. Prototype은 매번 새 인스턴스를 생성하므로 상태를 가질 수 있지만, Spring이 생성 후 추적하지 않아 @PreDestroy가 호출되지 않습니다."
"Singleton에 Prototype을 직접 주입하면 Singleton 생성 시점에 한 번만 주입되어 Prototype의 "매번 새 인스턴스" 특성이 무시됩니다. 해결 방법으로는 ObjectProvider를 사용해 매번 새 인스턴스를 받는 방법이 가장 권장됩니다."
"Singleton Bean은 Thread-Safe하지 않나요?" 이 질문에 당황한 적 있나요? 많은 개발자가 Spring Bean의 기본 스코프가 singleton이라는 사실은 알지만, 그것이 메모리와 스레드 안전성에 어떤 영향을 미치는지는 명확히 이해하지 못합니다.
면접관이 정말 듣고 싶은 건 "언제 singleton을 쓰고, 언제 prototype을 써야 하는지", 그리고 "두 스코프를 섞어 쓸 때 어떤 함정이 있는지"입니다. 이 아티클에서는 인터랙티브 시각화로 Bean Scope의 동작 원리를 완벽히 이해하고, 실무에서 마주치는 문제를 예방하는 방법을 배웁니다.
1. Bean Scope의 개념과 5가지 종류
꼬리질문: "Spring이 제공하는 Bean Scope 종류를 모두 설명해주세요"
Spring IoC 컨테이너는 Bean을 "언제 만들고, 얼마나 오래 유지하며, 누구와 공유할 것인가?"를 스코프(Scope)로 결정합니다. Spring은 singleton, prototype, request, session, application 총 5가지 스코프를 제공하며, 각 스코프는 인스턴스 생성 시점과 생명주기가 다릅니다.
기본값은 singleton으로, 대부분의 Bean이 상태를 가지지 않는 서비스 객체이기 때문에 인스턴스 하나를 재사용하는 것이 효율적입니다.
각 스코프 카드를 클릭하면 해당 스코프의 생명주기 타임라인이 펼쳐집니다.
싱글톤(Singleton) — 생명주기 타임라인
컨테이너 시작 시 1회 생성
모든 요청에서 같은 인스턴스 공유
컨테이너 종료 시 @PreDestroy 호출
2. Singleton vs Prototype — 같은 인스턴스 vs 매번 새로운 인스턴스
꼬리질문: "Singleton Bean은 Thread-Safe한가요?" / "Prototype Bean의 @PreDestroy가 호출되지 않는 이유는?"
Singleton은 컨테이너 시작 시 한 번만 생성되어 모든 주입 지점에서 같은 인스턴스를 공유합니다. Prototype은 getBean()이나 @Autowired로 요청할 때마다 새 인스턴스를 생성합니다. 이 차이는 메모리 사용량, 성능, Thread-Safety에 모두 영향을 미칩니다.
Singleton의 함정은 Thread-Safety가 보장되지 않는다는 것입니다. 여러 스레드가 같은 인스턴스의 필드에 접근하므로, 상태를 가지면 데이터 오염이 발생합니다. Prototype은 매번 새 인스턴스를 만들어 상태를 가질 수 있지만, @PreDestroy가 호출되지 않아 리소스 정리를 직접 해야 합니다.
"시뮬레이션 시작" 버튼을 클릭하면 동일한 요청 시나리오에서 두 스코프의 인스턴스 생성 패턴을 비교할 수 있습니다.
3개 컴포넌트에 Bean 주입 시 인스턴스 생성 패턴 비교
싱글톤(Singleton)
인스턴스 1개프로토타입(Prototype)
주입마다 새 인스턴스메모리 사용량 비교
* 프로토타입은 주입 횟수에 비례해 인스턴스가 늘어납니다. @PreDestroy가 호출되지 않으므로 GC가 정리합니다.
3. Request/Session Scope와 ThreadLocal 기반 동작
꼬리질문: "Request Scope Bean은 어떻게 HTTP 요청별로 분리되나요?"
웹 환경에서는 Request Scope(HTTP 요청마다), Session Scope(HTTP 세션마다) Bean이 유용합니다. Request Scope Bean은 같은 요청 내에서 Controller와 Service가 같은 인스턴스를 공유하므로, 요청 컨텍스트(요청 ID, 시작 시간, 사용자 정보)를 저장하는 데 적합합니다.
Spring은 ThreadLocal을 사용해 Request Scope를 구현합니다. HTTP 요청이 도착하면 Tomcat이 스레드를 할당하고, Spring이 RequestContextHolder에 요청 정보를 바인딩합니다. Request Scope Bean을 요청하면 해당 ThreadLocal에서 인스턴스를 조회하거나 생성하며,Session Scope는 여러 요청에 걸쳐 같은 인스턴스를 공유합니다.
"시뮬레이션 실행" 버튼을 클릭하면 2개 요청이 어떻게 별도 인스턴스를 사용하는지 확인할 수 있습니다.
두 HTTP 요청이 별도 ThreadLocal에서 RequestContext를 독립적으로 처리합니다
4. Singleton에 Prototype 주입 시 문제와 4가지 해결 방법
꼬리질문: "Singleton에 Prototype을 주입할 때 문제를 해결하는 방법에는 무엇이 있나요?"
Singleton Bean에 Prototype Bean을 @Autowired로 직접 주입하면 Singleton 생성 시점에 한 번만 주입되어, Prototype의 "매번 새 인스턴스" 특성이 무시됩니다. 이는 상태를 가진 Prototype Bean에서 데이터 오염과 메모리 누수를 일으킵니다.
해결 방법은 4가지입니다: ObjectProvider(Spring 표준, 가장 권장), Provider(JSR-330 표준), @Lookup(CGLIB 메서드 오버라이드), Scoped Proxy(프록시가 매번 새 인스턴스에 위임). 각 방법은 침투성, 테스트 용이성, 유연성에서 차이가 있으며, ObjectProvider가 getIfAvailable() 등 부가 메서드를 갖춰 가장 권장됩니다.
탭을 전환하며 문제 상황과 4가지 해결 방법의 동작 차이를 비교해 보세요.
@Service
public class OrderService {
@Autowired
private OrderProcessor processor; // 항상 같은 인스턴스!
}인스턴스 생성 패턴
방법 평가
자주 발생하는 문제
실무에서 자주 마주치는 3가지 Bean Scope 관련 문제입니다.
문제 1: Singleton Bean에서 공유 상태로 인한 데이터 오염
상황: 사용자 정보를 Singleton Bean의 필드에 저장
@Service
public class UserService {
private User currentUser; // 위험!
public void login(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return this.currentUser;
}
}증상: 사용자 A가 로그인했는데 사용자 B의 정보가 보이며, 간헐적으로 발생하여 재현이 어렵습니다.
원인: 여러 스레드가 같은 Singleton 인스턴스의 필드를 동시에 수정하여 Race Condition이 발생합니다.
해결: 상태를 가지지 않도록 하고, 필요한 정보는 파라미터나 SecurityContext에서 획득합니다.
@Service
public class UserService {
public User getCurrentUser() {
return SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
}
}문제 2: Prototype Bean 주입 후 동일 인스턴스 사용
상황: Prototype으로 선언했지만 매번 같은 인스턴스가 사용됨
@Component
@Scope("prototype")
public class RequestTracker {
private List<String> events = new ArrayList<>();
}
@RestController
public class ApiController {
@Autowired
private RequestTracker tracker; // 항상 같은 인스턴스!
@GetMapping("/api/action")
public void action() {
tracker.addEvent("action"); // events가 계속 쌓임 → 메모리 누수!
}
}증상: 시간이 지날수록 메모리 사용량이 증가하고, events 리스트가 무한히 커지며, 결국 OutOfMemoryError가 발생합니다.
원인: Singleton Controller에 Prototype Bean을 직접 주입하면 Controller 생성 시점에 한 번만 주입됩니다.
해결: ObjectProvider를 사용해 매번 새 인스턴스를 받습니다.
@RestController
public class ApiController {
@Autowired
private ObjectProvider<RequestTracker> trackerProvider;
@GetMapping("/api/action")
public void action() {
RequestTracker tracker = trackerProvider.getObject();
tracker.addEvent("action");
}
}문제 3: Prototype Bean의 리소스 미해제
상황: Prototype Bean이 외부 리소스(DB 커넥션, 파일 핸들)를 사용
@Component
@Scope("prototype")
public class ReportGenerator {
private Connection connection;
@PostConstruct
public void init() {
this.connection = dataSource.getConnection();
}
@PreDestroy // 호출되지 않음!
public void cleanup() {
connection.close();
}
}증상: 커넥션 풀이 고갈되고 "Cannot acquire connection" 에러가 발생하며, 시스템 리소스가 누수됩니다.
원인: Spring은 Prototype Bean의 @PreDestroy를 호출하지 않습니다.
해결: 수동으로 리소스를 정리하거나 try-with-resources를 사용합니다.
@Service
public class ReportService {
@Autowired
private ObjectProvider<ReportGenerator> generatorProvider;
public Report generate() {
ReportGenerator generator = generatorProvider.getObject();
try {
return generator.createReport();
} finally {
generator.cleanup(); // 명시적 정리
}
}
}면접 체크리스트
이 항목들을 자신 있게 설명할 수 있다면 Bean Scope 질문은 준비 완료입니다.
- - Singleton: 컨테이너당 1개, 메모리 효율적, Stateless 필수, 대부분의 서비스/Repository
- - Prototype: 요청마다 생성, 상태 가능, @PreDestroy 미호출, 리소스 정리 주의
- - Request/Session: 웹 전용, ThreadLocal 기반, 요청/세션 범위 데이터 저장
- - Singleton Thread-Safety: 보장되지 않음 — 상태를 가지면 Race Condition 발생
- - Singleton ← Prototype 주입 문제: ObjectProvider 사용 권장
- - 더 알아볼 주제: Spring AOP와 Scoped Proxy 내부 동작, Custom Scope 구현, ApplicationContext 계층 구조
Singleton Bean의 Thread-Safety
@Service
public class OrderService {
private int orderCount = 0;
public void processOrder() {
orderCount++;
System.out.println("주문 번호: " + orderCount);
}
}동시에 100개의 요청이 processOrder()를 호출하면 어떤 문제가 발생할까요?
Prototype Bean의 생명주기
@Component
@Scope("prototype")
public class FileProcessor {
private FileWriter writer;
@PostConstruct
public void init() throws IOException {
writer = new FileWriter("output.txt");
}
@PreDestroy
public void cleanup() throws IOException {
writer.close(); // 리소스 정리
}
}이 Bean을 10번 사용하면 cleanup() 메서드는 몇 번 호출될까요?
Request Scope의 동작 이해
@Component
@RequestScope
public class RequestContext {
private String userId;
}
@RestController
public class UserController {
@Autowired private RequestContext context;
@GetMapping("/user")
public String getUser() {
context.setUserId("user123");
return userService.findUser();
}
}
@Service
public class UserService {
@Autowired private RequestContext context;
public String findUser() {
return context.getUserId(); // ???
}
}UserService의 findUser()는 무엇을 반환할까요?
Singleton에 Prototype 주입 문제
@Component
@Scope("prototype")
public class DateFormatter {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String format(Date date) {
return sdf.format(date);
}
}
@Service
public class ReportService {
@Autowired
private DateFormatter formatter;
public String generateReport(Date date) {
return formatter.format(date);
}
}이 코드의 문제점은 무엇인가요?
Application Scope와 Singleton의 차이
// 하나의 Tomcat에서 2개의 ApplicationContext 실행
// (ContextA, ContextB)
@Component
@ApplicationScope
public class GlobalCounter {
private AtomicInteger count = new AtomicInteger(0);
}ContextA에서 count를 5까지 증가시킨 후, ContextB에서 count 값은 무엇일까요?
참고 자료
- Tecoble(우아한테크코스) — 스프링 빈 스코프(Bean Scope) — 우아한테크코스 기술 블로그에서 Bean Scope를 한국어로 쉽게 풀어쓴 입문 가이드
- Baeldung — Quick Guide to Spring Bean Scopes — 각 스코프의 사용 예제와 차이점을 친절하게 설명하는 영문 튜토리얼
- Baeldung — Injecting Prototype Beans into Singleton — Singleton-Prototype 주입 문제와 해결 방법을 실제 코드로 보여주는 실용적인 가이드
- HowToDoInJava — Scoped Bean Injection Problem — 스코프 간 주입 문제를 그림과 함께 쉽게 설명하는 튜토리얼
- LogicBig — ObjectProvider for Prototype Beans — ObjectProvider의 사용법과 내부 동작을 상세히 다루는 영문 가이드
의견을 들려주세요
서비스 개선에 큰 도움이 됩니다. 익명으로 자유롭게 남겨주세요.