Bean 이 가지는 생명주기(lifecycle) 이다.
보통은 스프링 컨테이너가 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때 까지 유지되는 싱글톤 스코프로 생성이 된다.
스코프의 종류
빈 스코프 등록
자동등록
@Scope("prototype")
@Component
public class HelloBean {}
수동등록
@Scope("prototype")
@Bean
PrototypeBean HelloBean() {
return new HelloBean();
}
default -- > @Scope("prototype")
핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다.
클라이언트에 빈을 반환하고 나면, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다.
관리책임은 클라이언트에게 넘어가게 된다.
프로토타입 스코프 빈 테스트
public class PrototypeTest {
@Test
void prototypeBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
System.out.println("find prototypeBean1");
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
System.out.println("find prototypeBean2");
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
System.out.println("prototypeBean1 = " + prototypeBean1);
System.out.println("prototypeBean2 = " + prototypeBean2);
Assertions.assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
ac.close();
}
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("SingletonBean.iit");
}
@PreDestroy
public void destroy() {
System.out.println("SingletonBean.destroy");
}
}
}
실행결과
find prototypeBean1
PrototypeBean.init
find prototypeBean2
PrototypeBean.init
prototypeBean1 = hello.core.scope.PrototypeTest$PrototypeBean@13d4992d
prototypeBean2 = hello.core.scope.PrototypeTest$PrototypeBean@302f7971
org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing
정리하자면,
스프링 컨테이너에 프로토타입 스코프 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다.
하지만 싱글톤 빈과 함께 사용할 시 의도대로 동작하지 않는다.
clientBean
은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도clientBean
은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을clientBean
에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.clientBean
은 프로토타입 빈을 내부 필드에 보관한다. (정확히는 참조값을 보관한다.)clientBean
을 스프링 컨테이너에 요청해서 받는다.싱글톤이므로 항상 같은clientBean
이 반환된다.clientBean.logic()
을 호출한다.clientBean
은 prototypeBean의 addCount()
를 호출해서 프로토타입 빈의 count를 증가한다.clientBean
을 스프링 컨테이너에 요청해서 받는다.싱글톤이므로 항상 같은clientBean
이 반환된다.clientBean.logic()
을 호출한다.clientBean
은 prototypeBean의 addCount()
를 호출해서 프로토타입 빈의 count를 증가한다.@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);
}
@Scope("singleton")
static class ClientBean {
private final PrototypeBean prototypeBean; // 생성시점에 주입
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@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");
}
}
스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그런데 싱글톤
빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과
함께 계속 유지되는 것이 문제다.
하지만 의도는 프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때 마다 새로 생성해서 사용하는 것을 원한다.