빈 스코프는 빈이 존재할 수 있는 범위를 뜻한다.
컨테이너에 싱글톤 스코프 빈을 요청하면, 컨테이너는 본인이 관리하는 빈을 반환해준다. (Single Instance)
컨테이너에 프로토타입 스코프 빈을 요청하면, 컨테이너는 새로운 프로토타입 빈을 생성하고, DI, 초기화 콜백함수 수행 후 클라이언트에 반환한다. 각 요청마다 같은 방식으로 새로운 빈을 생성해 반환하며, 반환 후에는 라이프싸이클에 관여하지 않는다.
초기화 콜백만 수행되기에, @PreDestroy는 수행되지 않는다.
프로토타입 빈은 요청마다 새로운 빈이 생성되어야 한다.
하지만 프로토타입 빈이 Singleton 빈의 DI를 통해 생성되고, Singleton 빈을 통해 접근하여 로직을 호출한다면, 해당 프로토타입 빈은 이미 싱글톤 빈의 생성시점에 싱글톤 빈에 주소값이 고정되어 있게 된다.
즉, Scope는 Prototype이지만 마치 Singleton과 같이 하나의 인스턴스에서만 동작하게 된다.
해당 문제를 방지하기 위해서는 2가지 방법이 있다.
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()이 호출될 경우, 컨테이너에 새로운 프로토타입 빈을 생성요청하여 로직이 수행되도록 할 수 있다.
하지만 해당 방식은 아래의 단점들이 있다.
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 는 스프링에 의존적이지 않다. (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는 아래와 같이 활용될 수 있다고 자바 표준 코드에 주석으로 작성되어 있다.
참고하도록 하자