🔹 스프링 컨테이너에 프로토타입 스코프 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다. 하지만 싱글톤 빈과 함께 사용할 때는 의도대로 동작하지 않기 때문에 주의해야 한다.
🔹 클라이언트A와 클라이언트B는 프로토타입 빈을 스프링 컨테이너에 직접 요청했기 때문에 각자 다른 객체를 가진다. 따라서 addCount()를 호출하고 반환받는 값은 각각 1로 동일하다.
🔹 싱글톤 clientBean은 스프링 컨테이너 생성 시점에 생성되고 의존 관계 주입이 발생한다.
🔹 의존관계 주입 시점에 스프링 컨테이너한테 프로토타입 빈을 요청한다.
🔹 스프링 컨테이너가 프로토타입 빈을 생성해서 clientBean에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.
🔹 clientBean은 내부 필드에 프로토타입 빈 참조값을 보관한다.
🔹 클라이언트A,B는 스프링 컨테이너에 clientBean 요청시 항상 같은 싱글톤 빈을 반환받는다.
🔹 clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가시킨다.
💡 clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이기 때문에 사용할 때마다 새로 생성되는 것이 아니다.
(참고) 여러 빈에서 같은 프로토타입 빈을 주입 받으면 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다.
❓ 싱글톤 빈과 프로타입 빈을 함께 사용할 때 항상 새로운 프로토타입 빈을 생성하고 싶다면 어떻게 해야할까?
🔹 가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새로 요청하는 것이다.
public class PrototypeProviderTest{
@Test
void providerTest(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean clinetBean1 = ac.getBean(ClientBean.class);
ClientBean clinetBean2 = ac.getBean(ClientBean.class);
}
}
public class PrototypeProviderTest{
static class ClientBean{
@Autowired
private ApplicationContext ac;
public int logic(){
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
}
🔹 실행시 ac.getBean()을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
🔹 의존관계를 외부에서 주입(DI) 받는게 아니라 직접 의존관계를 찾는 Dependency Lookup(DL) 의존관계 조회(탐색) 과정이 일어난다.
🔹 하지만 스프링의 애플리케이션 컨텍스트 전체를 주입받게 되면 스프링 컨테이너에 종속적인 코드가 되고 단위 테스트가 어려워진다.
💡 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL 정도의 기능만 제공하는 무언가가 필요하다.
🔹 ObjectFactory: 기능이 단순하고 별도의 라이브러리가 필요없다. 스프링에 의존한다.
🔹 ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리 등 편의 기능이 많고 별도의 라이브러리가 필요없다. 스프링에 의존한다.
🔹 ObjectProvider는 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공한다.
🔹 ObjectFactory에 편의 기능을 추가하여 ObjectProvider가 만들어졌다.
@Autowired
private ObjectProvider<PrototypeBean> prototyeBeanProvider;
public int logic(){
PrototypeBean prototypeBean = prototyeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
🔹 OjbectProvider의 getObject()를 호출하면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환하는 Dependency Lookup(DL) 기능을 제공한다.
🔹 javax.inject.Provider라는 JSR-330 자바 표준을 사용하는 방법
@Autowired
private Provider<PrototypeBean> provider;
public int logic(){
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
🔹 provider.get()을 통해 항상 새로운 프로토타입 빈이 생성된다.
🔹 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(DL)
🔹 자바 표준이고 기능이 단순하기 때문에 단위테스트를 만들거나 mock 코드를 만들기 쉽다.
🔹 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
🔹 별도의 라이브러리가 필요하다.
❓ 프로토타입 빈을 언제 사용할까?
❗ 매번 의존관계 주입이 완료된 새로운 객체가 필요하면 사용한다. 하지만 실무에서 웹 애플리케이션을 개발해보면 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 사용하는 일은 드물다.
❓ 실무에서 자바 표준 JSR-330 Provider를 사용할지 스프링이 제공하는 ObjectProvider를 사용할지 고민이 된다면?
❗ ObjectProvider는 DL을 위한 편의 기능을 많이 제공해주고 스프링 외의 별도의 의존관계 추가가 없어서 편리하다. 만약 코드를 다른 스프링이 아닌 다른 컨테이너에서 사용해야 한다면 JSR-330 Provider를 사용하자.(하지만 그럴일 없음) 대부분 스프링이 더 다양하고 편리 기능을 제공해주기 때문에 특별히 다른 컨테이너를 사용할 일이 없다면 스프링이 제공하는 기능을 사용하면 된다.