[스프링 핵심 원리] 9. 빈 스코프

건우·2025년 12월 3일

Back-end / Java, Spring

목록 보기
9/14
post-thumbnail

빈 스코프 — “이 빈, 언제까지 살아있냐?”

지금까지는 스프링 빈이 컨테이너 뜰 때 생성돼서, 컨테이너 죽을 때까지 살아있는 걸 기본으로 봤다.
이게 가능한 이유는 스프링 빈 기본이 싱글톤 스코프라서 그렇다.

여기서 스코프(scope)는 말 그대로
“이 빈이 존재할 수 있는 범위(수명)”를 뜻한다.


1. 스프링이 제공하는 대표 스코프들

  • singleton: 기본값. 컨테이너 시작~종료까지 쭉.
  • prototype: 요청할 때마다 새로 만들고, 만들고 주입까지만 하고 관리는 안한다.
  • 웹 스코프
    • request: HTTP 요청 1개 동안
    • session: 세션 동안
    • application: 서블릿 컨텍스트 동안
    • (websocket 등도 있다.)

등록 예시는 이런 느낌:

  • 컴포넌트 스캔: @Component + @Scope("prototype")
  • 수동 등록: @Bean + @Scope("prototype")

2. 프로토타입 스코프 — “요청할 때마다 새로 뽑는 빈”

싱글톤은 getBean()을 여러 번 해도 항상 같은 인스턴스를 준다.
근데 프로토타입은 getBean()할 때마다 항상 새 인스턴스를 만든다.

중요한 포인트

프로토타입 빈은 스프링이 여기까지만 해준다:

  • 생성
  • 의존관계 주입
  • 초기화

그리고 클라이언트에 넘겨준 다음부터는
“이제 너가 알아서 관리해~”
모드가 된다.

그래서 프로토타입 빈은 @PreDestroy 같은 종료 콜백이 자동으로 안 불린다.
(종료 책임은 빈을 받아간 쪽이 가져감)


3. 싱글톤 + 프로토타입 같이 쓰면 터지는 함정

“싱글톤 안에 프로토타입 주입하면, 매번 새로 생성되겠지?”

아니다.

싱글톤 빈은 생성 시점에 딱 한 번 주입을 받는다.
즉, 프로토타입도 주입 시점에 한 번 생성된 그 인스턴스를 싱글톤이 계속 들고 있는다.

그래서 결과적으로는:

  • “프로토타입을 쓴다고 생각했는데”
  • 실제로는 “싱글톤이 프로토타입 하나를 평생 들고 있음”

이 상황이 된다.


4. 해결: “쓸 때마다 새로 달라”를 구현하는 방법

핵심은 단 하나
진짜 프로토타입 조회를 “주입 시점”이 아니라 “사용 시점”으로 미루기 이다.

컨테이너에 직접 매번 요청(ac.getBean)

가장 단순한 방법인데, 단점도 확실하다.

  • 싱글톤 빈이 ApplicationContext를 들고 있어야 함
  • 컨테이너 종속 + 단위테스트 불편

그래서 “진짜 필요한 기능(DL)만” 뽑아 쓰는 방식이 나온다.

ObjectProvider (스프링 제공)

  • objectProvider.getObject() 호출 순간에 빈을 찾아준다.
  • DL(조회)만 딱 제공한다.
  • 테스트도 상대적으로 편해진다.

JSR-330 Provider (자바 표준)

  • 부트 3 기준 jakarta.inject.Provider
  • provider.get() 하나로 끝이다. (진짜 단순)

결론적으로

  • 스프링 안에서만 살 거면 ObjectProvider가 편하다.
  • 컨테이너 독립성이 필요하면 Provider(자바 표준) 쪽이 깔끔하다.

그리고 현실적으로는 프로토타입 자체를 자주 쓰진 않고, “필요할 때 조회(DL) 지연” 용도로 Provider류가 더 많이 쓰인다.


5. 웹 스코프 — “웹 요청/세션 단위로 살아있는 빈”

웹 스코프는 특징이 좀 다르다.

  • 웹 환경에서만 동작
  • 프로토타입과 달리 스프링이 종료 시점까지 관리
    → 그래서 종료 메서드도 호출된다.

대표는 request 스코프가 있다.


6. request 스코프 예제에서 왜 에러가 났나?

request 스코프 빈은 요청이 와야 생성될 수 있다.

근데 싱글톤(컨트롤러/서비스)은 앱 뜰 때 만들어지면서 주입이 필요하니까
“아직 요청도 없는데 request 빈을 어떻게 주입해?”

이러면서 터진다.

해결 1. Provider로 지연 생성

컨트롤러/서비스에서 ObjectProvider.getObject()를 요청 중에 호출하게 만들면

  • 요청이 들어온 상태라 request 빈 생성 가능
  • 같은 요청이면 같은 request 빈 반환

해결 2. 프록시(proxyMode)로 “가짜를 먼저 주입”

@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS) 같은 걸 붙이면

  • 스프링이 진짜 MyLogger 대신 가짜 프록시를 먼저 주입해둔다.
  • 그리고 실제로 메서드 호출하는 순간에 “지금 요청 기준의 진짜 request 빈”을 찾아서 위임해준다.

즉, 코드 입장에서는 그냥 myLogger.log() 똑같이 쓰는데
뒤에서 프록시가 알아서 “요청 시점에 진짜를 연결”해주는 느낌을 받는다.


7. 정리

  • 기본은 싱글톤 스코프라서 빈이 오래 산다.
  • 프로토타입은 요청할 때마다 새로 만들어주긴 하는데, 스프링이 관리하지 않는다.(그래서 종료 콜백도 없다.)
  • 싱글톤에 프로토타입을 그냥 주입하면 “매번 새로”가 아니라 “한 번 주입된 거 계속”이라는 함정이 있다.
    • 그래서 Provider / 프록시처럼 조회 시점을 늦추는 방식이 필요하다.
  • 웹 스코프는 request/session 같은 “웹 단위 수명”이고, 스프링이 끝까지 관리해서 종료도 챙겨준다.
    • 대신 request 스코프는 “요청이 와야 생성 가능”이라 Provider/프록시가 자주 등장한다.

출처
스프링 핵심 원리 - 기본편 (김영한, 인프런, 2020)

0개의 댓글