Spring 스프링 빈 스코프(Bean Scope) 개념 정리

suover

spring

스프링 빈(Spring Bean)과 IoC 컨테이너

스프링 프레임워크의 핵심 철학은 IoC(Inversion of Control) 컨테이너를 통해 객체(빈, Bean)를 관리하고 의존성을 주입(DI, Dependency Injection)하는 것입니다.

  • IoC 컨테이너란 스프링 프레임워크가 제공하는 객체 생성·초기화·소멸 등을 관리하는 컨테이너입니다.
  • 이 컨테이너에 의해 관리되는 객체를 스프링 빈이라 합니다.

스프링은 애플리케이션 실행 과정에서 필요한 빈들을 생성하고 의존 관계를 설정해줍니다. 그리고 이렇게 등록된 스프링 빈을 사용할 때, 빈이 생성되어 있는지, 혹은 생성 시점이 어떤지 등은 크게 신경 쓰지 않아도 됩니다. 대신, 스프링이 제공하는 스코프(Bean Scope) 설정을 통해 각 빈의 라이프사이클과 생성 횟수를 제어할 수 있습니다.

스프링 빈 스코프란?

스프링 빈 스코프(Bean Scope)는 스프링 IoC 컨테이너가 빈의 라이프사이클과 생성·사용 범위를 어떻게 관리할 것인지를 결정하는 정책입니다.

스프링은 아래와 같은 여러 스코프를 제공합니다.

  1. Singleton: 애플리케이션 전체에서 단 하나의 인스턴스만 생성한다.
  2. Prototype: 요청 시마다 새로운 인스턴스를 생성한다.
  3. Request: 웹 요청(HTTP request)이 들어올 때마다 빈 인스턴스를 생성하고, 요청이 끝나면 소멸한다.
  4. Session: 웹 세션(HTTP session) 동안 빈 인스턴스를 유지하고, 세션이 종료되면 소멸한다.
  5. Application: 서블릿 컨텍스트(ServletContext) 단위로 빈 인스턴스를 관리한다.
  6. WebSocket: 웹소켓 세션(WebSocket session)마다 빈 인스턴스를 생성하고, 연결이 끊어지면 소멸한다.

사용자 애플리케이션의 동작 요구사항에 맞춰 스코프를 적절히 설정함으로써, 불필요한 리소스를 아끼고 적절한 시점에 필요한 인스턴스를 생성·관리할 수 있습니다.

주요 스코프별 특징과 예시 코드

Singleton 스코프

  • 특징
    • 스프링 컨테이너 시작 시점에 단 하나의 빈 인스턴스를 생성하여 애플리케이션 종료 전까지 계속 동일 객체를 사용.
    • 가장 기본적인 스코프이며, 설정을 따로 지정하지 않으면 기본값으로 singleton이 적용됨.
    • 메모리 상에 하나의 객체만 존재하므로 재사용성이 뛰어나지만, 상태가 있는(=내부에 멤버 변수가 존재하고 이를 수정하는) 빈은 동시성 문제를 일으킬 수 있음.
사용 예시
Java
@Service
public class SingletonService {
    public void doSomething() {
        // singleton 스코프의 예시
        System.out.println("SingletonService: 항상 동일한 인스턴스가 사용됩니다.");
    }
}
Java

Java
@Configuration
public class AppConfig {
    
    @Bean
    // @Scope("singleton") // 생략 시 singleton이 기본값
    public SingletonService singletonService() {
        return new SingletonService();
    }
}
Java

  • 별도로 @Scope(“singleton”)을 명시하지 않아도 자동으로 싱글톤 스코프가 적용됩니다.

  • 주의사항
    • 싱글톤 빈은 멀티스레드 환경에서 동시에 접근될 때, 내부 상태를 변경하는 로직이 있으면 동기화 또는 부작용이 발생하지 않도록 유의해야 합니다.
    • 무상태(Stateless)로 설계되어야 스레드 안전성(쓰레드 세이프)을 보장하기 편합니다.

Prototype 스코프

  • 특징
    • 스프링 컨테이너에 빈을 요청할 때마다(getBean() 호출 시마다) 새로운 인스턴스를 생성.
    • 컨테이너는 빈을 생성하고 의존관계를 주입만 해준 뒤, 이후의 빈 관리(소멸 등)는 사용자 측에 맡김.
    • 자주 변경될 수 있거나, 매번 새로운 인스턴스가 필요한 경우에 적합.
사용 예시
Java
@Component
@Scope("prototype")
public class PrototypeService {
    public PrototypeService() {
        System.out.println("PrototypeService 객체가 생성되었습니다.");
    }
}
Java

Java
@RestController
public class PrototypeController {
    
    @Autowired
    private ApplicationContext context;  // 스프링 컨텍스트를 직접 주입 받음
    
    @GetMapping("/prototype-test")
    public String prototypeTest() {
        PrototypeService service1 = context.getBean(PrototypeService.class);
        PrototypeService service2 = context.getBean(PrototypeService.class);

        // 두 객체는 다르다
        boolean same = service1 == service2;
        return "service1 == service2? " + same;
    }
}
Java

  • /prototype-test를 두 번 호출하면, 매번 새로운 PrototypeService 객체가 생성된다는 로그가 출력될 것입니다.

  • 주의사항
    • 소멸 시점: 프로토타입 스코프 빈은 컨테이너가 라이프사이클을 책임지지 않습니다. @PreDestroy 같은 라이프사이클 콜백도 호출되지 않으므로, 필요하다면 사용자 코드에서 직접 소멸 처리를 해야 합니다.
    • 프로토타입 빈 주입 시 주의: 싱글톤 빈이 프로토타입 빈을 주입받는 상황에서, 싱글톤 빈이 생성될 때 단 한 번만 프로토타입 빈이 생성되므로 이후에는 새로운 프로토타입 인스턴스를 주입받지 못합니다. 이러한 경우 ObjectFactory, Provider, ApplicationContext 등을 이용해 필요할 때마다 새로 의존성을 주입해 줘야 합니다.

Request 스코프

  • 특징
    • 웹 환경(Spring MVC 등)에서만 유효한 스코프.
    • HTTP 요청(HttpServletRequest)이 발생할 때마다 새로운 빈 인스턴스를 생성하고, 요청이 끝나면(response가 나간 뒤) 소멸.
    • 각 요청마다 독립적인 객체가 필요할 때 사용. 예: 요청마다 고유한 로깅 정보를 저장하거나, 트랜잭션과 연동된 요청 스코프 의존성이 필요한 경우.
사용 예시
Java
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopeBean {
    private final LocalDateTime createdAt = LocalDateTime.now();

    public String getCreationTime() {
        return createdAt.toString();
    }
}
Java

Java
@RestController
public class RequestScopeController {

    private final RequestScopeBean requestScopeBean;

    // 생성자 주입
    public RequestScopeController(RequestScopeBean requestScopeBean) {
        this.requestScopeBean = requestScopeBean;
    }

    @GetMapping("/request-scope")
    public String getRequestScopeCreationTime() {
        return "RequestScope Bean 생성 시간: " + requestScopeBean.getCreationTime();
    }
}
Java

  • @Scope(“request”)로 사용이 가능하지만, 명확하게 WebApplicationContext.SCOPE_REQUEST를 쓰기도 합니다.
  • proxyMode를 사용하면, 요청이 없는 시점에도 해당 빈을 주입받아 쓰고자 할 때 문제가 발생하지 않도록 프록시 객체를 통해 접근하게 됩니다.

  • 주의사항
    • Request 스코프 빈은 HTTP 요청이 없는 시점에서는 존재하지 않습니다. 따라서 다른 싱글톤 빈에서 직접 주입받을 경우 스프링 컨테이너가 이를 생성할 수 없으므로, proxyMode를 통해 지연 로딩(프록시) 기법을 사용해야 하는 경우가 많습니다.
    • 웹 애플리케이션 환경에서만 동작하므로, 일반 자바 애플리케이션 환경에서는 사용이 불가능합니다.

Session 스코프

  • 특징
    • 웹 환경에서만 유효.
    • HTTP 세션(HttpSession)이 유효한 동안 동일한 빈 인스턴스를 유지하고, 세션이 만료(혹은 무효화)되면 소멸.
    • 주로 로그인 사용자 정보나, 세션 단계에서 유지해야 할 데이터를 관리할 때 사용.
사용 예시
Java
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopeBean {
    private final String sessionId;

    public SessionScopeBean() {
        // 세션 스코프이므로, 각 사용자 세션마다 인스턴스가 하나씩 생성
        this.sessionId = UUID.randomUUID().toString();
    }

    public String getSessionId() {
        return sessionId;
    }
}
Java

Java
@RestController
public class SessionScopeController {

    private final SessionScopeBean sessionScopeBean;

    public SessionScopeController(SessionScopeBean sessionScopeBean) {
        this.sessionScopeBean = sessionScopeBean;
    }

    @GetMapping("/session-scope")
    public String getSessionScope() {
        return "Session ID: " + sessionScopeBean.getSessionId();
    }
}
Java

  • 다른 요청이라도 같은 세션이라면 SessionScopeBean 인스턴스가 동일합니다.

  • 주의사항
    • 세션 스코프 빈에 의존성이 있는 경우, 세션이 만료되면 해당 빈도 더 이상 유효하지 않습니다.
    • 다중 사용자 환경에서, 세션 스코프를 통한 데이터 관리는 세션별로 분리되어야 하므로 주의 깊은 설계가 필요합니다.
    • 또한 세션을 너무 장시간 사용하거나 대량의 데이터를 저장하면 메모리 누수가 발생할 수 있으므로 조심해야 합니다.

Application 스코프

  • 특징
    • 웹 애플리케이션의 서블릿 컨텍스트(ServletContext) 전역에서 빈을 하나만 생성하여 공유.
    • @Scope(value = WebApplicationContext.SCOPE_APPLICATION)로 설정.
    • 애플리케이션 전체 범위에서 공용 데이터를 다룰 때 사용하지만, 싱글톤 스코프와 크게 다를 것이 없다고 착각하기 쉽습니다. 실제로는 서블릿 컨텍스트 단위이므로, 스프링 컨텍스트가 여러 개 있을 경우(멀티 컨텍스트 환경) 주의해야 합니다.
사용 예시
Java
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public class ApplicationScopeBean {
    private final AtomicInteger counter = new AtomicInteger(0);

    public int incrementAndGet() {
        return counter.incrementAndGet();
    }
}
Java

Java
@RestController
public class ApplicationScopeController {
    private final ApplicationScopeBean applicationScopeBean;

    public ApplicationScopeController(ApplicationScopeBean applicationScopeBean) {
        this.applicationScopeBean = applicationScopeBean;
    }

    @GetMapping("/application-scope")
    public String applicationScopeTest() {
        int currentCount = applicationScopeBean.incrementAndGet();
        return "Application Scope Count: " + currentCount;
    }
}
Java

  • 여러 사용자가 GET /application-scope를 호출해도 같은 ApplicationScopeBean이 공유되어 카운트가 계속 올라갑니다.

WebSocket 스코프

  • 특징
    • 스프링 5부터 추가된 기능으로, 웹소켓 세션마다 빈 인스턴스를 생성하고 세션이 종료되면 소멸.
    • @Scope(“websocket”) 또는 @Scope(value = “websocket”, proxyMode = ScopedProxyMode.TARGET_CLASS)로 선언.
    • 실시간 데이터 통신을 웹소켓으로 구현하는 경우, 해당 연결 세션별로 필요한 데이터를 저장하거나 상태를 유지하는 데 사용 가능.
사용 예시
Java
@Component
@Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketScopeBean {
    private final String id = UUID.randomUUID().toString();

    public String getId() {
        return id;
    }
}
Java

  • WebSocket 핸들러 또는 컨트롤러에서 의존성을 주입받아 사용할 수 있습니다.

스코프 적용 시 주의해야 할 사항

  1. 빈의 라이프사이클 관리
    • 싱글톤을 제외한 다른 스코프(프로토타입, request, session, websocket 등)는 생성과 소멸 시점이 달라집니다.
    • 프로토타입 스코프는 스프링 컨테이너가 빈을 ‘생성’만 하고 ‘소멸’은 직접 관리하지 않으므로 리소스 누수(leak)에 유의해야 합니다.
  2. 스레드 안전성(Thread-Safety) 이슈
    • 싱글톤 스코프의 빈은 애플리케이션 전체가 공유하므로 멀티 스레드 환경에서 동시 접근 시 적절한 동기화 처리가 이루어져야 합니다.
    • 다만 request/session/websocket 스코프 빈은 각 요청/세션별로 인스턴스가 분리되어 생성·관리되므로 상대적으로 상태 관리가 편합니다.
  3. 프록시(Proxy) 모드 사용
    • 스코프가 웹 요청과 관련된 request, session, websocket 등일 때는, 싱글톤과 달리 빈 생성 시점이 특별합니다.
    • 애플리케이션 구동 시점에 싱글톤 빈이 이런 스코프 빈을 직접 주입받으려 하면, 아직 해당 스코프가 존재하지 않아 예외가 발생할 수 있습니다.
    • @Scope(…, proxyMode = ScopedProxyMode.TARGET_CLASS) 설정을 통해 프록시 객체를 만들어 두고, 실제 요청(세션, 웹소켓)이 발생했을 때 실제 빈을 생성하여 연결해줍니다. 이를 지연 로딩(lazy loading)이라고 합니다.
  4. 빈 주기 메서드(PostConstruct, PreDestroy)
    • 싱글톤 및 기타 웹 스코프(request, session, application, websocket 등)는 @PostConstruct와 @PreDestroy가 정상 호출됩니다.
    • 하지만 프로토타입은 @PreDestroy가 자동 호출되지 않으니, 필요한 경우 직접 객체 소멸 로직을 호출해야 합니다.
  5. 미세한 의존성 관리
    • 싱글톤 빈이 프로토타입 빈을 주입받을 때는, 매번 새로 프로토타입 객체를 받으려면 ObjectFactory, Provider(자바 표준 javax.inject.Provider, 또는 스프링의 ObjectProvider)를 사용해야 합니다.
    • 그렇지 않으면 의도와 달리 프로토타입 빈이 한 번만 만들어져 계속 같은 빈을 사용하게 됩니다.

스코프 설정 방법과 정리

1. XML 기반 설정
  • 예전 버전 스프링 사용 시, beans XML에서 <bean scope=”singleton”>와 같이 설정할 수 있습니다.
2. 자바 기반 설정
  • 현재는 대부분 자바(어노테이션) 기반 설정을 사용합니다.
Java
@Bean
@Scope("prototype")
public MyBean myBean() {
    return new MyBean();
}
Java

  • 클래스 레벨에서 @Component와 함께 @Scope(“prototype”), @Scope(“request”) 등으로 지정할 수도 있습니다.

주요 스코프 정리

  • Singleton: 애플리케이션 전체에서 단 한 개의 인스턴스를 공유.
  • Prototype: 빈 요청 시마다 새 인스턴스 생성.
  • Request: HTTP 요청 단위로 빈을 생성하고, 요청 종료 시 폐기.
  • Session: HTTP 세션 단위로 빈을 생성·유지, 세션 종료 시 폐기.
  • Application: 서블릿 컨텍스트 단위로 빈을 생성·유지.
  • WebSocket: 웹소켓 연결 세션 단위로 빈을 생성·유지, 연결 종료 시 폐기.

언제 어떤 스코프를 사용해야 할까?

  • 싱글톤(Singleton)
    • 대부분의 서비스나 컴포넌트가 사용하는 기본 스코프.
    • 서버 전체에서 공유되는 ‘무상태’(stateless) 로직이 많으면 싱글톤 사용이 일반적입니다.
  • 프로토타입(Prototype)
    • 요청마다 새로운 객체가 꼭 필요한 경우.
    • 예: 대화형 툴이나 매번 새로운 인스턴스로 작업해야 하는 경우, 혹은 생성 후 특정 라이프사이클을 컨테이너가 아닌 사용자가 직접 관리해야 하는 경우.
  • Request/Session 스코프
    • 웹 애플리케이션에서, 요청 단위·세션 단위로 따로 상태를 유지해야 할 때 사용.
    • 특히 사용자별 세션 관리가 필요한 로직은 session 스코프가 자연스럽습니다.
  • Application 스코프
    • 사실상 싱글톤과 유사하나, 서블릿 컨텍스트가 여러 개 분리된 환경(멀티 모듈 또는 멀티 컨텍스트)에서 유용하게 사용될 수 있음.
    • 특별히 구분해야 할 이유가 없다면 싱글톤만으로도 충분한 경우가 많습니다.
  • WebSocket 스코프
    • 실시간 양방향 통신에서 각 연결 세션별로 독립적인 상태 관리를 해야 할 때 유용.

결론

스프링 빈 스코프는 빈의 생성 시점과 유지·소멸 시점을 결정함으로써, 애플리케이션 동작에 큰 영향을 미칩니다. 알맞은 스코프를 사용하면 메모리 사용 최적화와 동시성 안전성 등을 향상시킬 수 있고, 잘못 사용하면 디버깅이 어려운 상태 관리 문제나 리소스 누수 등 다양한 문제에 직면하게 됩니다.

  1. 싱글톤은 기본 스코프로 무상태와 재사용이 필요한 대부분의 서비스 로직에 적합합니다.
  2. 프로토타입은 매번 다른 객체가 필요한 경우나, 객체 생성을 프로그래머가 직접 제어해야 할 때 유용하나, 소멸 처리에 주의해야 합니다.
  3. 웹 스코프(Request, Session, Application, WebSocket)는 웹 애플리케이션 환경에서 특정 범위에 따라 빈의 생명주기를 관리하여 보다 편리하고 정확한 상태 관리를 가능하게 합니다.
  4. 싱글톤 빈과 웹 스코프 빈을 함께 사용하거나, 멀티스레드 환경에서 싱글톤 빈의 상태를 관리할 때는 동시성 문제를 예방해야 합니다.
  5. 스코프 사용 시 프록시(Scoped Proxy)를 이용하여 의존성을 연결하거나, @Lazy, ObjectProvider 등을 통해 필요 시점에 빈을 주입받는 설계를 고려할 수 있습니다.

정리하자면, “어떤 범위의 생명주기를 가진 객체가 필요한가?”라는 질문에 따라 스프링 빈 스코프를 적절히 지정하는 것이 매우 중요합니다. 각 스코프별 특징과 동작 방식을 정확히 이해하고 상황에 맞게 적용하면, 더욱 안정적이고 효율적인 스프링 애플리케이션을 구현할 수 있을 것입니다.

Leave a Comment