티스토리 뷰

Spring

[Spring] @RequestScope

snail voyager 2026. 1. 10. 02:28
728x90
반응형

기본 개념

  • 기본적으로 스프링 빈은 싱글톤(@Singleton) 이라서 애플리케이션 시작 시 한 번 생성되고 컨테이너가 내려갈 때까지 유지됩니다.
  • 하지만 웹 애플리케이션에서는 요청마다 다른 값을 유지해야 하는 경우가 많습니다.
    (예: 로그인 사용자 정보, 요청 ID, 트랜잭션 관련 데이터 등)
  • 이럴 때 @RequestScope를 사용하면 요청 단위로 새로운 빈을 생성할 수 있습니다.

사용 예시

  • /test API를 두 번 호출하면 매번 새로운 requestId가 생성됩니다.
  • 왜냐하면 RequestInfo 빈이 요청마다 새로 만들어지기 때문이에요.
@Component
@RequestScope
class RequestInfo {
    val requestId: String = UUID.randomUUID().toString()
}

@RestController
class TestController(
    private val requestInfo: RequestInfo
) {
    @GetMapping("/test")
    fun test(): String {
        return "Request ID: ${requestInfo.requestId}"
    }
}

동작 방식

  • @RequestScope는 내부적으로 Spring AOP의 프록시(proxy) 를 사용합니다.
  • 싱글톤 빈 안에서 @RequestScope 빈을 주입받을 수 있는 이유는, 스프링이 실제 객체 대신 프록시 객체를 주입해주고, 요청이 들어올 때 해당 프록시가 알맞은 요청 스코프 객체로 바인딩해주기 때문이에요.

주의할 점

  1. Web Application Context에서만 동작합니다. (즉, spring-web 환경이 필요)
    → 일반 애플리케이션에서는 사용할 수 없음.
  2. 스레드 세이프(thread-safe) 하지 않습니다. 요청 단위로만 보장되기 때문에, 동시에 여러 요청이 들어오면 각 요청마다 별도의 인스턴스가 생성됩니다.
  3. @RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) 같이 프록시 모드를 지정하는 경우가 많습니다. (@RequestScope 자체가 @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)의 축약형)

왜 사용하는가?

  1. 싱글톤 빈에 요청 단위 데이터를 저장하면 안 되기 때문
    • 스프링 빈은 기본적으로 싱글톤 → 모든 요청에서 공유됨.
    • 요청마다 달라지는 데이터를 싱글톤에 저장하면 동시성 문제 발생.
    • 따라서 요청마다 새 인스턴스를 주는 @RequestScope가 필요.
  2. 코드의 책임을 명확히 분리
    • "요청 단위 데이터 관리"를 별도 빈으로 분리해서, 컨트롤러/서비스가 비즈니스 로직에 집중할 수 있음.
  3. AOP, 로깅, 트랜잭션 맥락 관리에 유용
    • 요청 단위로 고유한 ID, 사용자 정보, 트레이싱 데이터 등을 묶어 관리 가능.

대표적인 사용 사례

1) 요청 ID 로깅 (트레이싱)

@Component
@RequestScope
class RequestContext {
    val requestId: String = UUID.randomUUID().toString()
}

@RestController
class TestController(
    private val context: RequestContext
) {
    @GetMapping("/ping")
    fun ping(): String {
        return "Request ID = ${context.requestId}"
    }
}
  • 요청마다 고유한 requestId가 발급 → 로그에 찍어서 트레이싱 가능.

2) 사용자 인증 정보 보관

@Component
@RequestScope
class UserContext(
    val userId: String = SecurityContextHolder.getContext().authentication.name
)

@Service
class OrderService(
    private val userContext: UserContext
) {
    fun createOrder(): String {
        return "User ${userContext.userId} 주문 생성"
    }
}
  • 컨트롤러마다 매번 SecurityContextHolder에서 사용자 정보를 꺼낼 필요 없음.
  • UserContext 빈을 주입받아서 사용 → 깨끗한 코드 유지.

3) 요청 단위 캐싱

@Component
@RequestScope
class RequestCache {
    val cache: MutableMap<String, Any> = mutableMapOf()
}

@Service
class ProductService(
    private val requestCache: RequestCache
) {
    fun getProduct(id: String): Any {
        return requestCache.cache.getOrPut(id) {
            // DB 호출 or 외부 API 호출
            loadProduct(id)
        }
    }
}
  • 같은 요청 내에서 반복적으로 같은 데이터를 조회하면 → 요청 단위 캐시로 성능 최적화 가능.

4) 에러 핸들링/응답 공통 구조

@Component
@RequestScope
class ErrorCollector {
    val errors: MutableList<String> = mutableListOf()
}
  • 요청 처리 중 발생한 여러 검증 에러를 모아서 컨트롤러 응답에 담을 때 사용.

 

728x90
반응형

'Spring' 카테고리의 다른 글

[Spring] DispatcherServlet 동작 흐름  (0) 2025.05.02
[Spring] 예외 처리 우선 순위  (0) 2025.05.01
[Spring] @Valid vs. @Validated  (0) 2025.04.29
[Spring Boot] OpenFeign 쿼리스트링 객체 매핑  (0) 2025.02.24
[Spring] @ModelAttribute  (0) 2025.01.30
반응형
300x250