Spring

[Spring 완전 정복 시리즈] 21편 - 빈 스코프 완전 정복 (1): 싱글톤과 프로토타입

dev-nadan 2025. 8. 1. 13:46

이번 편부터는 스프링 빈의 생존 범위를 제어하는 ‘스코프(scope)’에 대해 알아본다.

특히 실무에서 잘못 이해하고 사용하면 큰 문제가 되는 프로토타입 스코프와 웹 스코프도 예제와 함께 꼼꼼히 정리해본다.

 


빈 스코프란?

 

스프링 컨테이너는 일반적으로 애플리케이션 시작 시점에 빈을 생성하고, 종료 시점에 함께 소멸시킨다.

이는 기본적으로 빈이 싱글톤 스코프로 등록되기 때문이다.

 

여기서 스코프(Scope)란, “빈이 존재할 수 있는 생존 범위“를 의미한다.


스프링이 지원하는 주요 스코프

스코프 이름설명

스코프 이름 설명
singleton 기본 스코프. 컨테이너 시작~종료까지 같은 인스턴스 사용
prototype 요청할 때마다 새로운 빈 생성. 생성·의존 주입·초기화까지만 컨테이너가 관리
request 하나의 HTTP 요청 동안 유지됨
session 하나의 HTTP 세션 동안 유지됨
application 서블릿 컨텍스트와 동일한 생존 범위

 


프로토타입 스코프란?

@Scope("prototype")
@Component
public class PrototypeBean {
    @PostConstruct
    public void init() {
        System.out.println("PrototypeBean.init");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("PrototypeBean.destroy");
    }
}
  • ac.getBean(PrototypeBean.class)을 호출할 때마다 새로운 인스턴스를 생성함
  • 하지만 스프링 컨테이너는 생성, 의존성 주입, 초기화까지만 관여
  • 종료 메서드(@PreDestroy)는 호출되지 않음

 

즉, 프로토타입 빈의 생애주기는 다음과 같다:

 

생성 → 의존 주입 → 초기화 → (컨테이너가 관리 종료) → 사용자가 직접 관리해야 함

싱글톤과 프로토타입의 차이 실험

싱글톤 테스트

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
SingletonBean bean1 = ac.getBean(SingletonBean.class);
SingletonBean bean2 = ac.getBean(SingletonBean.class);
System.out.println(bean1 == bean2); // true

 

프로토타입 테스트

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean bean1 = ac.getBean(PrototypeBean.class);
PrototypeBean bean2 = ac.getBean(PrototypeBean.class);
System.out.println(bean1 == bean2); // false

싱글톤 빈에서 프로토타입 빈을 주입하면?

다음과 같이 싱글톤 빈이 프로토타입 빈을 생성자 주입받는다면?

@Scope("singleton")
@Component
public class ClientBean {
    private final PrototypeBean prototypeBean;

    public ClientBean(PrototypeBean prototypeBean) {
        this.prototypeBean = prototypeBean;
    }

    public int logic() {
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }
}

이 경우 프로토타입 빈이 하나만 생성되어 매번 같은 인스턴스를 재사용하게 된다.

이것은 프로토타입의 의도와는 어긋나는 상황이다.


해결 방법: 매번 새로 빈을 요청하고 싶다면?

1. ApplicationContext를 이용한 DL

@Autowired
private ApplicationContext applicationContext;

public int logic() {
    PrototypeBean prototypeBean = applicationContext.getBean(PrototypeBean.class);
    return prototypeBean.logic();
}

→ 하지만 ApplicationContext에 직접 의존하게 되므로 테스트가 어렵고 코드도 지저분해짐


2. ObjectProvider 또는 javax.inject.Provider 사용

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;

public int logic() {
    PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
    return prototypeBean.logic();
}

 

  • getObject()를 호출할 때마다 새로운 빈을 반환
  • 단위 테스트와 유지보수도 용이
  • 실무에서는 주로 ObjectProvider 또는 표준인 javax.inject.Provider를 사용

마무리

  • 스프링 빈은 다양한 스코프를 통해 생존 범위를 제어할 수 있다.
  • 프로토타입 빈은 생성 이후 스프링이 더 이상 관리하지 않으며, 클라이언트가 책임을 져야 한다.
  • 싱글톤 빈에서 프로토타입 빈을 사용하려면 Provider나 DL 방식을 고려해야 한다.