본문 바로가기

Spring

[Spring] 빈 스코프(Bean Scope)


빈 스코프(Bean Scope)란?

먼저 빈(Bean)은 스프링 컨테이너에서 관리하는 자바 객체입니다. 그리고 스코프(Scope)는 존재할 수 범위를 말합니다.

즉, 빈 스코프는 스프링 빈이 존재할 수 있는 범위를 뜻합니다.

빈 스코프를 어떻게 설정하느냐에 따라 스프링 빈의 생성과 소멸을 클라이언트에서 관리해줘야 하는 경우도 생길 수 있고, 다양한 요구사항에 맞는 스코프를 지정해 사용할 수 있습니다.

 

Scope Description
singleton (Default) 각 스프링 컨테이너에 대한 단일 객체 인스턴스에 대한 단일 bean definition의 범위를 지정합니다
prototype 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프입니다.
request 웹 요청이 들어오고 나갈때 까지 유지되는 스코프입니다.
session 웹 세션이 생성되고 종료될 때까지 유지되는 스코프입니다.
application 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프입니다.
websocket 단일 bean definition 범위를 WebSocket의 라이프사이클까지 확장합니다. 
Spring ApplicationContext의 Context에서만 유효합니다.

 

  • 특정 Bean definition에서 생성된 개체에 연결할 다양한 의존성 및 구성 값뿐만이 아닌 특정 bean definition에서 생성된 개체의 범위도 제어할 수 있습니다.
  • 스프링 프레임워크는 위의 6개의 범위를 지원하며, 그중 4개는 ApplicationContext를 사용하는 경우에만 사용할 수 있습니다.
  • 빈은 여러 범위 중 하나에 배치되도록 정의할 수 있습니다.
  • 구성을 통해 생성하는 개체의 범위를 선택할 수 없기 때문에 강력하고 유연합니다.
  • 사용자 정의 범위를 생성할 수 도 있습니다.

 

singleton scope


 

 

 

위의 그림 예제와 같이 싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 빈을 반환합니다.

 

프로토타입 스코프


 

 

프로토타입 스코프의 빈을 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환합니다.

 

프로토타입의 빈을 스프링 컨테이너에 요청하면 스프링 컨테이너는 프로토타입의 빈을 생성하고, 필요한 의존관계를 주입합니다. 

싱글톤 빈 컨테이너 생성 시점에 같이 생성되고 초기화되지만, 프로토타입 빈은 스프링컨테이너에서 빈을 조회할 때 생성되고 초기화 메서드도 실행됩니다.

 

스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계를 주입하고, 초기화까지만 처리합니다. 클라이언트에게 빈을 반환한 이후에는 생성된 프로토타입 빈을 관리하지 않습니다. 프로토타입 빈을 관리할 책임은 클라이언트에게 있습니다.

따라서 @PreDestory와 같은 종료 콜백 메서드가 호출되지 않습니다.

 

프로토타입 빈의 문제점

만약 싱글톤 빈과 프로토타입 빈을 같이 사용하면 어떻게 될까요?

아래의 그림을 보도록 하겠습니다.

 

 

위의 그림처럼 clientBean이 프로토타입 빈을 포함한다고 하겠습니다.

clientBean은 싱글톤이므로, 스프링 컨테이너 생성 시점에서 함께 생성되고, 의존 관계 주입도 발생합니다.

 

1. clientBean은 의존관계 자동 주입을 사용합니다. 주입 시점에 스프링 컨테이너에게 프로토타입 빈을 요청합니다.

2. 스프링 컨테이너는 프로토타입 빈에 대한 요청을 받고, 프로토타입 빈을 생성해서 clientBean에게 반환됩니다.

3. 프로토타입 빈의 count 필드 값은 0입니다.

 

ClientBean은 프로토타입 빈을 내부 필드에 보관합니다.

 

 

클라이언트 A는 clientBean을 스프링 컨테이너에 요청해서 받습니다. 

싱글톤이므로 항상 같은 clientBean이 반환됩니다.

 

4. 클라이언트 A는 clientBean.logic()을 호출합니다.

5. clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가시킵니다. 

6. count의 값이 1이 됩니다.

 

클라이언트 B는 clientBean을 스프링 컨테이너에 요청해서 받습니다. 싱글톤이므로 항상 같은 clientBean이 반환됩니다.

여기에서 clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈입니다.

주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이지, 사용할 때마다 새로 생성되는 것이 아닙니다.

 

7. 클라이언트 B는 clientBean.logic()을 호출합니다.

8. clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가시킵니다.

9. count의 값이 1이었으므로 2가 됩니다.

 

각 요청마다 count 값을 1로 반환받는 것을 기대하였지만 2를 반환받게 됩니다. 결국 싱글톤과 프로토타입을 함께 사용하는 경우 기대한 대로 동작하지 않는 것입니다.

 

스프링은 일반적으로 singleton 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 됩니다.

하지만 싱글톤 빈은 생성 시점에서만 의존 관계를 주입받습니다.

따라서 스프링 싱글톤 빈이 생성되는 시점에 프로토타입 빈도 새로 생성되어서 주입되긴 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제입니다.

 

따라서 프로토타입 빈을 주입 시점에만 새로 생성하는 것이 아니라, 사용할 때마다 새로 생성해 사용해야 합니다.

 

웹 스코프


웹 스코프는 웹 환경에서만 동작합니다. 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리합니다. 따라서 종료 메서드가 호출됩니다. 웹 스코프는 다음과 같은 종류가 있습니다.

 

 

  • request: HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프입니다. 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리됩니다.
  • session : HTTP Session과 동일한 생명주기를 가지는 스코프입니다.
  • application: 서블릿 콘텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프입니다.
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프입니다.

 

스프링 빈 등록 시 웹 스코프를 그대로 주입받으면 오류가 발생합니다. 싱글톤 빈은 스프링 컨테이너 생성 시 함께 생성되어서 라이프 사이클을 같이하지만, 웹 스코프의 경우 HTTP 요청이 올 때 새로 생성되고 응답하면 사라지기 때문에, 싱글톤 빈이 생성되는 시점에는 아직 생성되지 않았습니다.

따라서 의존 관계 주입이 불가능합니다.

 

이때 프록시를 사용하면 문제를 해결할 수 있습니다.

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
  • @Scope 속성에 proxyMode = ScopedProxyMode.TARGET_CLASS를 추가해 줍니다.
    • 적용 대상이 인터페이스가 아닌 클래스면 TARGET_CLASS를 선택
    • 적용 대상이 인터페이스면 INTERFACES를 선택
  • 위 방법을 통해 MyLogger의 가짜 프록시프락시 클래스를 만들어두고 HTTP request와 상관없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있습니다.
  • Scope의 proxyMode = ScopedProxyMode.TARGET_CLASS를 설정하면 스프링 컨테이너는 CGLIB이라는 바이트코드 조작 라이브러리를 사용해서, 해당 웹 스코프 빈 클래스를 상속하는 가짜 프록시 객체를 생성합니다.
  • 위의 결과를 보면 내가 등록한 순수 자바 클래스 빈이 아니라 가짜 프록시 객체가 등록된 것을 확인할 수 있습니다.
  • 그리고 스프링 컨테이너에 원래 만들었던 진짜 클래스 이름으로 진짜 대신 가짜 프락시 객체를 등록합니다.
  • ag.getBean("myLogger", MyLogger.class)로 조회해도 프록시 객체가 조회됩니다.
  • 위 방법을 통해 싱글톤 빈이 생성되는 시점의 의존 관계 주입 때 이 가짜 프록시 객체가 주입됩니다.

 

 

  • 가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있습니다.
  • 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있습니다.
  • 클라이언트가 myLogger.logic()을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것입니다.
  • 가짜 프록시 객체는 request 스코프의 진짜 myLogger.logic()을 호출합니다.
  • 가짜 프록시 객체는 원본 클래스를 상속받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 원본인지 아닌지도 모르게 동일하게 사용할 수 있습니다.(다형성)

Reference

https://code-lab1.tistory.com/186

'Spring' 카테고리의 다른 글

[Spring] 컴포넌트 스캔(Component Scan)  (0) 2023.04.07
[Spring] 싱글톤(Singleton)  (0) 2023.04.06
[Spring] 빈(Bean)  (0) 2023.04.04
[Spring] 스프링 컨테이너(Spring Container)  (0) 2023.04.03
[Spring] DI(Dependency Injection)란?  (0) 2023.04.02