1. Spring Bean Scope란?
싱글톤 패턴이란?
싱글톤(Singleton) 패턴은 클래스의 인스턴스를 하나만 생성하여 전역적으로 공유하는 디자인 패턴입니다. 생성자를 private으로 막고 정적 메서드를 통해 유일한 인스턴스를 반환하는 방식으로 구현됩니다.
- 사용 목적: 메모리 절약, 인스턴스 재사용, 설정이나 캐시 등의 전역 상태 공유
- 적용 사례: 설정 클래스, 로깅 유틸, 스프링의 서비스 클래스
Spring은 이러한 싱글톤 패턴을 프레임워크 차원에서 기본 지원합니다. 대부분의 Bean은 특별한 설정이 없으면 singleton 스코프로 관리되며, 이는 내부적으로 싱글톤 패턴처럼 동작합니다.
하지만 모든 상황에 싱글톤이 적합한 것은 아닙니다. 요청(Request)마다 새로운 객체가 필요하거나, 사용자(Session)마다 상태를 분리해야 하는 경우에는 다른 스코프가 필요합니다.
따라서 Spring에서는 다양한 Bean Scope 설정을 통해 개발자가 목적에 따라 인스턴스 생명주기를 조절할 수 있도록 돕습니다.
싱글톤 Bean의 동작 방식과 주의점
Spring에서 싱글톤 스코프의 Bean은 하나의 인스턴스를 여러 요청이 공유하게 됩니다. 하지만 이는 요청이 순차적으로 처리된다는 뜻은 아닙니다. Spring Boot의 내장 웹 서버는 멀티스레드 기반으로 작동하기 때문에 동시에 들어온 요청들도 모두 같은 싱글톤 인스턴스를 동시에 참조하게 됩니다. 즉, 10명의 사용자가 같은 API를 동시에 호출하더라도 각각의 요청은 다른 스레드에서 처리되며, 같은 Bean 인스턴스를 공유합니다.
이로 인해 싱글톤 Bean 내부에서 상태를 가지는 필드가 있다면 동시성 문제가 발생할 수 있습니다. 따라서 싱글톤은 반드시 stateless하게 설계하는 것이 원칙입니다.
문제 사례
@Service
public class BadService {
private String username; // 상태를 가지는 필드
public void setUser(String name) {
this.username = name;
}
public String getUser() {
return this.username;
}
}
위 코드는 여러 요청이 동시에 setUser()와 getUser()를 호출하면 값이 꼬일 수 있습니다.
이 문제는 public으로 선언되었기 때문이 아니라, Bean이 싱글톤이면서 상태(state)를 내부 필드로 보존하고 있기 때문에 생기는 문제입니다. 즉, 필드가 private여도 상태가 공유되면 동일하게 위험합니다.
하지만, 실제로 MVC 아키텍처에서는 DTO를 이용해 요청 값을 주고받는 구조가 일반적이며, 이 DTO들은 매 요청마다 생성되므로 상태 공유 문제가 발생하지 않습니다. 또한 서비스 계층은 대부분 stateless하게 구성되며, 필요한 데이터는 DB에서 조회하거나 파라미터로 전달받기 때문에 싱글톤의 상태 공유 문제와는 거리가 멉니다.
2. 주요 Scope 종류
Scope 이름 | 생성 범위 | 사용 예시 및 설명 |
singleton | ApplicationContext 당 1개 생성 | 기본값. 서비스, DAO 등 재사용 가능한 컴포넌트 |
prototype | 요청할 때마다 새로 생성됨 | 상태가 독립적인 객체 (ex: 동적 파라미터 기반 생성) |
request | HTTP 요청마다 1개 생성 | 요청 단위의 로깅/검증 객체, DTO 처리 (Web 환경) |
session | HTTP 세션마다 1개 생성 | 로그인 사용자 정보 저장, 세션 기반 상태 유지 |
application | ServletContext 당 1개 | 전역 설정이나 공유 캐시 |
request, session, application 스코프는 Spring Web (Servlet) 환경이 구성된 경우에만 사용 가능합니다. 즉, Spring Boot에서도 spring-boot-starter-web 의존성을 포함한 웹 프로젝트에서는 정상적으로 동작하지만, CLI 또는 테스트 환경에서는 스코프 등록 오류가 발생할 수 있습니다.
3. 실전 예시별 적용 스코프 비교
실전 시나리오 | 권장 Scope | 이유 |
API 서비스 로직 (UserService 등) | singleton | 상태 없음, 재사용성 중요 |
매 요청마다 검사하는 Validator | request | 요청 간 상태 분리 필요 |
로그인한 유저 정보 저장 | session | 세션 단위로 유지되어야 함 |
요청마다 DTO 새로 만들기 | prototype | 파라미터 기반 객체 필요 |
4. request + session 복합 사용 예시
로그인 사용자 정보를 저장하고, 요청마다 그 값을 검증하는 시나리오를 예로 들어봅니다.
SessionUser.java
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionUser {
private String username;
private LocalDateTime loginTime = LocalDateTime.now();
// getter/setter
}
RequestLogger.java
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestLogger {
public RequestLogger() {
System.out.println("[RequestLogger] New instance created");
}
}
Controller 예시
@RestController
public class UserController {
@Autowired
private SessionUser sessionUser;
@Autowired
private RequestLogger requestLogger;
@GetMapping("/user")
public String getUser() {
System.out.println("RequestLogger: " + requestLogger.hashCode());
return "Hello, " + sessionUser.getUsername();
}
}
5. 테스트 시 주의사항
- request, session 스코프는 Spring Boot Test 환경에서 WebApplicationContext로 테스트해야 정상 동작합니다.
- @WebMvcTest 또는 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 등을 사용해야 함.
- 일반 JUnit 테스트에서는 IllegalStateException: No Scope registered for scope 'request' 발생 가능.
6. 요약
Scope | 언제 사용하나? |
singleton | 대부분의 경우 기본 선택 |
prototype | 독립적인 상태를 가진 객체가 필요한 경우 |
request | 요청 단위 분리 처리 시 (ex. API 로깅, 유효성 검증) |
session | 사용자별로 로그인 상태나 설정을 유지해야 할 때 |
'Back-End > Java & Spring' 카테고리의 다른 글
Proxy 패턴 개념&Spring AOP (0) | 2025.05.11 |
---|---|
Factory 패턴 개념 (0) | 2025.05.11 |
Spring & Spring Boot에서 자주 쓰이는 디자인 패턴 정 (0) | 2025.05.11 |
Java GC 로그 해석과 튜닝 전략 정리 (0) | 2025.05.10 |
Java GC 알고리즘 정리 (0) | 2025.05.10 |