Spring Bean Scope (Singleton & Prototype)

Hansu Kim·2022년 2월 20일
0

Bean Scope란?

빈 스코프는 빈이 존재할 수 있는 범위를 뜻한다.

Bean Scope의 종류

  • Singleton: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지
  • Prototype: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
  • 웹 관련 Scope
    • request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

Singleton Scope

컨테이너에 싱글톤 스코프 빈을 요청하면, 컨테이너는 본인이 관리하는 빈을 반환해준다. (Single Instance)

Prototype Scope

컨테이너에 프로토타입 스코프 빈을 요청하면, 컨테이너는 새로운 프로토타입 빈을 생성하고, DI, 초기화 콜백함수 수행 후 클라이언트에 반환한다. 각 요청마다 같은 방식으로 새로운 빈을 생성해 반환하며, 반환 후에는 라이프싸이클에 관여하지 않는다.

초기화 콜백만 수행되기에, @PreDestroy는 수행되지 않는다.

Prototype 빈과 Singleton 빈을 함께 사용할 때의 문제점

프로토타입 빈은 요청마다 새로운 빈이 생성되어야 한다.
하지만 프로토타입 빈이 Singleton 빈의 DI를 통해 생성되고, Singleton 빈을 통해 접근하여 로직을 호출한다면, 해당 프로토타입 빈은 이미 싱글톤 빈의 생성시점에 싱글톤 빈에 주소값이 고정되어 있게 된다.

즉, Scope는 Prototype이지만 마치 Singleton과 같이 하나의 인스턴스에서만 동작하게 된다.

해당 문제를 방지하기 위해서는 2가지 방법이 있다.

  • Dependency Lookup
  • ObjectFactory, ObjectProvider
  • JSR-330 Provider

Dependency Lookup

AnnotationConfigApplicationContext ac = new
  AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
  
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
assertThat(count1).isEqualTo(1);

ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
assertThat(count2).isEqualTo(1);

static class ClientBean {
	@Autowired
    private ApplicationContext ac;
    public int logic() {
    	PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
	}
}
 

DL은 DI와 다르게, 의존관계를 외부에서 주입(DI) 받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 말한다.
위 코드에서, ClientBean은 싱글톤 빈이며, 컨테이너를 의존 관계로 주입받고 있다.
컨테이너를 DI 받았기에, 컨테이너를 통해 프로토타입 빈을 getBean할 수 있고, 이를 통해 싱글톤 빈 내의 logic()이 호출될 경우, 컨테이너에 새로운 프로토타입 빈을 생성요청하여 로직이 수행되도록 할 수 있다.

하지만 해당 방식은 아래의 단점들이 있다.

  • 싱글톤 빈이 컨테이너 전체를 주입받아야 함.
  • 그렇게 되면 컨테이너 종속적이게 되어, 테스트 코드 작성도 어렵고, 단위 테스트도 어렵고, 스프링 컨테이너에 종속적이게 됨.

ObjectFactory, ObjectProvider

Dependency Lookup 방식은 전체 컨테이너를 주입받아야하는 단점이 있었다.
하지만 만약, 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공해주는 객체가 있다면 컨테이너 전체를 DI 받을 필요가 없다.

ObjectProvider는 지정한 빈을 컨테이너에서 대신 찾아주는 역할을 수행한다.
정확히는, ObjectProvider.getObject()를 호출하면 컨테이너에서 해당 빈을 찾아서 반환한다.

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
	PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}
  • ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
  • ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존

JSR-330 Provider

JSR-330 Provider 는 스프링에 의존적이지 않다. (javax.inject.Provider)
하지만 build.gradle에 해당 라이브러리('javax.inject:javax.inject:1')를 추가해주고, gradle을 Refresh해주어야만 사용할 수 있다.

static class ClientBean {
	@Autowired
    private Provider<PrototypeBean> prototypeBeanProvider;
    @Autowired
    private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;

	public int logic() {
    	PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
        PrototypeBean prototypeBean2 = prototypeBeanProvider.get();
        prototypeBean2.addCount();
        int count = prototypeBean2.getCount();
        return count;
	}
}

Provider가 활용되는 경우

Provider는 아래와 같이 활용될 수 있다고 자바 표준 코드에 주석으로 작성되어 있다.
참고하도록 하자

0개의 댓글