suyeonme

[Spring] Bean Scope 종류 본문

프로그래밍👩🏻‍💻/Spring

[Spring] Bean Scope 종류

suyeonme 2022. 6. 5. 19:12

Scope란 bean이 존재할 수 있는 범위를 의미한다. 아래와 같이 @Scope 애노테이션을 사용하여 bean의 스코프를 지정할 수 있다.

@Scope("prototype")
@Component
public class BeanTest()

Scope의 종류

스프링에서 지원하는 스코프의 종류는 아래와 같다.

1. Singleton (default)

2. Prototype

3. Web 관련

1. Singleton Scope

기본(default) 스코프로, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.

  • 스프링 빈(bean)을 등록하면 기본적으로 싱글톤 스코프이다.
  • 빈(bean)을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환한다.
  • 스프링 컨테이너 생성 시점에 초기화 메서드가 실행된다.
@Scope("singleton")
public class SingleTonBean {}

2. Prototype Scope

스프링 컨테이너가 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더이상은 관리하지 않는 짧은 범위의 스코프이다.

  • 프로토타입 빈은 스프링 컨테이너에 요청할 때마다 새로운 인스턴스로 생성된다. 따라서 사용할 때마다 의존 관계 주입이 완료된 새로운 객체가 필요한 경우에 사용한다.
  • 스프링컨테이너는 프로토타입 빈의 생성, 의존 관계 주입, 초기화까지만 처리한다. (@PreDestory와 같은 종료메서드가 호출되지 않음) 따라서 프로토타입 빈을 호출한 클라이언트에서 관리해야한다.
  • 스프링 컨테이너에서 프로토타입 빈을 조회할 때 인스턴스가 생성되고 초기화 메서드가 실행된다.
@Scope("prototype")
public class PrototypeBean {}

2.1 Singleton Bean에서 Prototype Bean을 조회할 경우

싱글톤 빈은 생성 시점에 의존 관계를 주입받는다. 따라서 싱글톤 빈에서 프로토타입 빈은 사용할 경우, 프로토타입 빈이 새로 생성이 되지만, 의존 관계 주입을 받은 당시의 상태가 유지된다. (프로토타입 빈의 필드가 초기화가 되지 않음)

 

대부분의 경우에 Prototype bean을 사용하는 이유는, 빈 조회시 초기화가 완료된 새로운 인스턴스(bean)를 반환받고 싶어서일 것이다. 

이러한 경우, 아래의 방법으로 필요한 시점에 직접 Prototype Bean 의존 관계를 주입하여 해결할 수 있다.

1. ObjectProvider, ObjectFactory 사용

2. JSR-330 Provider 사용

 

 ObjectProvider, ObjectFactory 사용하여 해결 

ObjectProvider는 getObject()가 호출될 때 새로운 인스턴스를 생성하여 반환한다. 따라서 항상 새로운 인스턴스임이 보장된다.

 public class ClientBean {
  @Autowired
  private ObjectProvider<PrototypeBean> prototypeBeanProvider;

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

@Scope("prototype")
public class PrototypeBean {}

JSR-330 Provider 사용하여 해결

gradle의 dependency에 javax.inject:javax.injec:1를 추가한다. 그리고 JSR에서 제공하는 Provider.get()을 사용한다.

JSR은 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용이 가능하다.

public class ClientBean {
  @Autowired
  private Provider<PrototypeBean> prototypeBeanProvider;

  public int logic() {
    PrototypeBean prototypeBean = prototypeBeanProvider.get();
    prototypeBean.addCount();
  }
}

@Scope("prototype")
public class PrototypeBean {}

 

3. Web Scope

웹 환경에서만 동작하며, http request에 따라서 인스턴스를 생성하고 스프링 컨테이너가 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출된다.

 

예를 들면, 아래처럼 각각의 http request에 따라서  각자의 scope를 가진다.

  • A request → 인스턴스 생성 → A request 주기 동안 유지 -> 종료
  • B request → 인스턴스 생성 → B request 주기 동안 유지 -> 종료
  •  

웹 스코프는 아래 4가지로 분류된다.

1. Request: http 웹 요청이 들어오고 나갈때 까지 유지되는 스코프

2. Session: 웹 세션이 생성되고 종료될 때까지 유지되는 스코프

3. Application: 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프

4. Websocket: 웹소켓과 동일한 생명주기를 가지는 스코프

 

3.1 주의 사항

Request Scope를 사용할 경우, request가 들어와야 빈의 scope가 유효하다. 따라서 request가 들어오지 않은 상태에서 웹 스코프를 가진 빈을 조회하면 에러가 발생한다. 이러한 경우 아래 두가지 방법으로 해결할 수 있다.

 

1. ObjectProvider 사용

2. Proxy 사용

 

 ObjectProvider 사용

@RestController
public class LogController {
  private final ObjectProvider<MyLogger> = myLoggerProvider;

  @RequestMapping("log-demo")
  @ResponseBody
  public String logDemo(HttpServletRequest request) {
    String requestURL = request.getRequestURL().toString();
    // request가 들어왔을 때 의존성 주입
    MyLogger myLogger = myLoggerProvider.getObject();
    myLogger.setRequestURL(requestURL);
  }
}

 Proxy 사용

CGLIB라는 라이브러리(바이트코드 조작)로 해당 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다. 프록시 객체는 실제 요청이 오면 내부에서 실제 빈을 요청하는 위임 로직이 들어있기 때문에 실제 요청이 들어오면 그 때 진짜 bean의 로직을 실행한다.

  • INTERFACES: 적용 대상이 인터페이스(interface)인 경우
  • TARGET_CLASS: 적용 대상이 클래스인 경우
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {}
Comments