티스토리 뷰
프로토타입 빈과 싱글톤 빈을 함께 사용 시 문제점
프로토 타입 빈과 싱글톤 빈을 함께 사용하면 의도한 대로 잘 동작하지 않을 수 있다. 아래 코드를 통해 알아보았다.
예제
아래 코드는 싱글톤 빈 내부에 있는 프로토타입 빈이 있을 때 2개의 클라이언트가 각각 싱글톤을 주입받으면 내부에 있는 프로토타입이 새로 생성되는지 알아보는 코드이다.
public class SingletonWithPrototypeTest {
@Test
void singletonClientUsePrototype() {
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(2);
}
static class ClientBean {
private final PrototypeBean prototypeBean;
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init " + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
위의 코드를 실행하면 의도한대로 동작하지 않는 것을 볼 수 있다. (클라이언트가 2개의 싱글톤을 주입받았는데 각 싱글톤 내부에 있는 프로토타입 빈의 count 멤버변수가 증가하였다.) 왜 의도한 대로 동작하지 않을까? 이유는 컨테이너가 실행하면서 싱글톤 빈과 내부에 있는 프로토타입 빈을 생성하는데 싱글톤 빈은 딱 한 번만 생성 후 클라이언트가 호출 시 주입받기 때문에 클라이언트가 싱글톤을 계속 주입받아도 그 내부에 있는 프로토타입 빈은 새로 생성되지 않는다.
해결
싱글톤 빈을 주입 받았을 때 프로토타입 빈을 새로 생성하고 싶으면 아래와 같은 코드를 통해 구현하면 된다.
ObjectFactory, ObjectProvider
- ObjectFactory: 스프링 컨테이너를 통해 빈을 반환하는 기능이 있으며, 기능이 단순하며, 별도의 라이브러리가 필요 없고, 스프링에 의존한다.
- ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리 등 편의 기능이 많고, 별도의 라이브러리가 필요 없고, 스프링에 의존한다.
public class SingletonWithPrototypeTestSolution1 {
@Test
void singletonClientUsePrototype() {
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 ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init " + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
위의 코드에서 ObjectProvider를 통해 프로토타입 빈을 주입받았다. 원하는 대로 싱글톤 빈을 주입받을 때 프로토타입 빈도 함께 주입받을 수 있다. (ObjectProvider는 ObjectFactory를 상속받은 것이다.)
JSR-330 Provider
두 번째 방법이 있는데 자바 표준인 Provider를 사용하는 것이다. ObjectProvider과 기능은 비슷하지만 Provider는 자바 표준을 사용하는 것이다. 사용할려면 gradle를 추가해야 한다.
//스프링부트 3.0 미만
javax.inject:javax.inject:1
//스프링부트 3.0 이상
jakarta.inject:jakarta.inject-api:2.0.1
static class ClientBean {
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
위의 코드에서 똑같이 프로토타입 빈을 주입받을 수 있다. 기능은 매우 단순하지만 라이브러리가 필요하지만 자바 표준이므로 다른 컨테이너에서도 사용할 수 있다.
정리
위의 문제점에서 ObjectProvider과 Provider를 통해 해결해 보았다. ObjectProvider는 스프링환경에서 라이브러리를 추가할 필요가 없고 Provider는 추가해야 한다. 만약 스프링이 아닌 다른 컨테이너에서 사용한다면 Provider를 사용하면 되고(자바 표준이기 때문), 스프링 환경에서는 ObjectProvider를 사용하면 된다.
본 포스팅은 “스프링 핵심 원리 - 기본 편/인프런”를 학습한 내용을 정리한 것
'Java > Spring' 카테고리의 다른 글
| <Spring> 컨트롤러 객체 없이 뷰에 요청 전달하기 (0) | 2024.12.03 |
|---|---|
| <Spring> 의존 관계 주입 방법 (0) | 2024.08.27 |
| <Spring> 스프링 초기화 시점에서의 트랜잭션 적용 (0) | 2024.08.09 |
| <Spring> 트랜잭션 프록시 내부 호출시 문제점 (0) | 2024.07.29 |
| <Spring> @SessionAttribute 이란? (0) | 2024.07.25 |
