스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다.
하지만 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의해야 한다. 그림과 코드로 설명하겠다.
먼저 스프링 컨테이너에 프로토타입 빈을 직접 요청하는 예제를 보자.
프로토타입 빈 직접 요청
스프링 컨테이너에 프로토타입 빈 직접 요청1
- 1. 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청한다.
- 2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(**x01**)한다. 해당 빈의 count 필드 값은 0이다.
- 3. 클라이언트는 조회한 프로토타입 빈에 `addCount()` 를 호출하면서 count 필드를 +1 한다.
- 결과적으로 프로토타입 빈(**x01**)의 count는 1이 된다
스프링 컨테이너에 프로토타입 빈 직접 요청2
- 1. 클라이언트B는 스프링 컨테이너에 프로토타입 빈을 요청한다.
- 2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(**x02**)한다. 해당 빈의 count 필드 값은 0이다.
- 3. 클라이언트는 조회한 프로토타입 빈에 `addCount()` 를 호출하면서 count 필드를 +1 한다.
- 결과적으로 프로토타입 빈(**x02**)의 count는 1이 된다.
코드로 확인
package hello.core.scope;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class SingoletonWithPrototypeTest1 {
@Test
void protoTypeFild(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ProtoTypeBean.class);
ProtoTypeBean protoTypeBean1 = ac.getBean(ProtoTypeBean.class);
protoTypeBean1.addCound();
Assertions.assertThat(protoTypeBean1.getCount()).isEqualTo(1);
ProtoTypeBean protoTypeBean2 = ac.getBean(ProtoTypeBean.class);
protoTypeBean2.addCound();
Assertions.assertThat(protoTypeBean2.getCount()).isEqualTo(1);
}
@Scope("prototype")
static class ProtoTypeBean
{
private int count = 0;
public void addCound(){
count++;
}
public int getCount(){
return count;
}
@PostConstruct
public void init(){
System.out.println("PrototypeBean.init "+this);
}
@PreDestroy
public void destory(){
System.out.println("PrototypeBean.destory ");
}
}
}
싱글톤 빈에서 프로토타입 빈 사용
이번에는 `clientBean` 이라는 싱글톤 빈이 의존관계 주입을 통해서 프로토타입 빈을 주입받아서 사용하는 예를 보자.
싱글톤에서 프로토타입 빈 사용1
- `clientBean` 은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다.
- 1. `clientBean` 은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다.
- 2. 스프링 컨테이너는 프로토타입 빈을 생성해서 `clientBean` 에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.
- 이제 `clientBean` 은 프로토타입 빈을 내부 필드에 보관한다. (정확히는 참조값을 보관한다.)
싱글톤에서 프로토타입 빈 사용2
- 클라이언트 A는 `clientBean` 을 스프링 컨테이너에 요청해서 받는다.싱글톤이므로 항상 같은 `clientBean` 이 반환된다.
- 3. 클라이언트 A는 `clientBean.logic()` 을 호출한다.
- 4. `clientBean` 은 prototypeBean의 `addCount()` 를 호출해서 프로토타입 빈의 count를 증가한다.
- count값이 1이 된다.
싱글톤에서 프로토타입 빈 사용3
- 클라이언트 B는 `clientBean` 을 스프링 컨테이너에 요청해서 받는다.싱글톤이므로 항상 같은 `clientBean`이 반환된다.
- 여기서 중요한 점이 있는데, clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이
다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지, 사용 할 때마다 새로 생성되
는 것이 아니다! - 5. 클라이언트 B는 `clientBean.logic()` 을 호출한다.
- 6. `clientBean` 은 prototypeBean의 `addCount()` 를 호출해서 프로토타입 빈의 count를 증가한다.
- 원래 count 값이 1이었으므로 2가 된다.
테스트 코드
package hello.core.scope;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class SingoletonWithPrototypeTest1 {
@Test
void protoTypeFild(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ProtoTypeBean.class);
ProtoTypeBean protoTypeBean1 = ac.getBean(ProtoTypeBean.class);
protoTypeBean1.addCound();
Assertions.assertThat(protoTypeBean1.getCount()).isEqualTo(1);
ProtoTypeBean protoTypeBean2 = ac.getBean(ProtoTypeBean.class);
protoTypeBean2.addCound();
Assertions.assertThat(protoTypeBean2.getCount()).isEqualTo(1);
}
@Test
void singletonClientUsePrototype(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ProtoTypeBean.class, ClientBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
Assertions.assertThat(count1).isEqualTo(1);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
Assertions.assertThat(count2).isEqualTo(2);
}
@Scope("singleton")
static class ClientBean{
private final ProtoTypeBean protoTypeBean;
@Autowired
public ClientBean(ProtoTypeBean protoTypeBean){
this.protoTypeBean = protoTypeBean;
}
public int logic(){
protoTypeBean.addCound();
int count = protoTypeBean.getCount();
return count;
}
}
@Scope("prototype")
static class ProtoTypeBean
{
private int count = 0;
public void addCound(){
count++;
}
public int getCount(){
return count;
}
@PostConstruct
public void init(){
System.out.println("PrototypeBean.init "+this);
}
@PreDestroy
public void destory(){
System.out.println("PrototypeBean.destory ");
}
}
}
스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다.
그런데 싱글톤 빈은 생성시점에만 의존관계 주입을 받기 때문에,
프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는것 이 문제다.
아마 원하는 것이 이런 것은 아닐 것이다.
프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때 마다 새로생성해서 사용하는 것을 원할 것이다.
참고:
여러 빈에서 같은 프로토타입 빈을 주입 받으면, **주입 받는 시점에 각각 새로운 프로토타입 빈이 생성**된다.
예를 들어서 clientA, clientB가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입 빈을 주입 받는다.
clientA → prototypeBean@x01
clientB → prototypeBean@x02
물론 사용할 때 마다 새로 생성되는 것은 아니다.
'Spring > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
55. 웹 스코프 (0) | 2023.08.13 |
---|---|
54. 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Probider로 문제해결 (0) | 2023.08.13 |
52. 프로토타입 스코프 (0) | 2023.05.07 |
53. 빈 스코프란? (0) | 2023.05.07 |
52. 애노테이션 @PostConstruct, @PreDestroy (이 방법을 쓰면 된다) (0) | 2023.05.07 |