(인프런) 김영한님의 스프링 핵심 원리-기본편을 공부하고 리뷰한 글입니다.
3. 프로토타입 스코프(싱글톤 빈과 함께 사용시 문제점)
실무에서 프로토타입 빈을 사용하는 경우는 드물지만 사용할 경우 보통 싱글톤 빈과 함께 사용한다.
하지만 싱글톤 빈과 함께 사용할 때는 의도한대로 잘 동작하지 않으므로 주의해야한다.
1. 프로토타입 빈 직접 요청
스프링 컨테이너에 프로토타입 빈을 직접 요청하는 예제이다.
1) 스프링 컨테이너에 프로토타입 빈 직접 요청1
1) 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청한다.
2) 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01)한다. 해당 빈의 count 필드 값은 0이다.
3) 클라이언트는 조회한 프로토타입 빈에 addCount() 를 호출하면서 count 필드를 +1 한다.
→ 결과적으로 프로토타입 빈(x01)의 count는 1이 된다
2) 스프링 컨테이너에 프로토타입 빈 직접 요청2
1) 클라이언트B는 스프링 컨테이너에 프로토타입 빈을 요청한다.
2) 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x02)한다. 해당 빈의 count 필드 값은 0이다.
3) 클라이언트는 조회한 프로토타입 빈에 addCount() 를 호출하면서 count 필드를 +1 한다.
→ 결과적으로 프로토타입 빈(x02)의 count는 1이 된다.
3) prototypeFind() 테스트 코드
클라이언트1, 2가 스프링 컨테이너에 프로토타입 빈을 직접 조회했을 때의 테스트 코드다.
package hello.core.scope;
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;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class SingletonWithPrototypeTest1 {
@Test
void prototypeFind() {
// PrototypeBean 빈 등록
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
// 프로토타입 빈 조회1
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
assertThat(prototypeBean1).isEqualTo(1);
// 프로토타입 빈 조회2
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
assertThat(prototypeBean2).isEqualTo(1);
}
// 프로토타입 빈
@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");
}
}
}
4) 실행 결과
프로토타입 빈은 getBean()으로 빈을 조회할 때 빈 생성, 의존관계 주입, 초기화 메서드 호출이 일어난다.
위 코드에서 프로토타입 빈을 2번 조회했기때문에 빈 생성도 2번 되고, init() 메서드도 2번 호출되는 것을 확인할 수 있다.
2. 싱글톤 빈에서 프로토타입 빈 사용
ClientBean이라는 싱글톤 빈이 의존관계 주입을 통해 프로토타입 빈을 주입받아 사용하는 예제다.
1) 싱글톤에서 프로토타입 빈 사용1 - 스프링 컨테이너 생성(clientBean 생성, 의존관계 주입)
clientBean 은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 생성, 의존관계 주입이 일어난다.
1) clientBean 은 의존관계 자동 주입(생성자 주입)을 사용한다. clientBean 생성 시점에 의존관계 주입을 위해 스프링 컨테이너에 프로토타입 빈을 요청한다.
2) 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean 에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.
→ clientBean 은 프로토타입 빈의 참조값을 내부 필드에 보관한다.
2) 싱글톤에서 프로토타입 빈 사용2 - 클라이언트 A가 clientBean 요청
클라이언트 A는 스프링 컨테이너에 clientBean을 요청해서 받는다. clientBean은 싱글톤이므로 항상 같은 clientBean 이 반환된다.
3) 클라이언트 A는 clientBean.logic() 을 호출한다.
4) clientBean 은 prototypeBean의 addCount() 를 호출해서 프로토타입 빈의 count를 1 증가한다. count값이 1이 된다.
3) 싱글톤에서 프로토타입 빈 사용3 - 클라이언트 B가 clientBean 요청
클라이언트 B는 스프링 컨테이너에 clientBean을 요청해서 받는다. clientBean은 싱글톤이므로 항상 같은 clientBean 이 반환된다.
여기서 중요한 점은 clientBean이 내부에 가지고 있는 프로토타입 빈 = clientBean 생성 시점에 주입이 끝난 빈이다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지, 사용 할 때마다 새로 생성되는 것이 아니다!
5) 클라이언트 B는 clientBean.logic() 을 호출한다.
6) clientBean 은 prototypeBean의 addCount() 를 호출해서 프로토타입 빈의 count를 1 증가한다. 원래 count 값(1) + 1 -> 2가 된다.
4) 테스트 코드
package hello.core.scope;
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;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class SingletonWithPrototypeTest1 {
@Test
void singletonClientUsePrototype() {
// ClientBean, PrototypeBean 빈 등록
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
// clientBean(싱글톤빈) 조회1
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
assertThat(count1).isEqualTo(1);
// clientBean(싱글톤빈) 조회2
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
assertThat(count2).isEqualTo(2);
}
// 싱글톤 빈
@Scope("singleton") // 생략O
static class ClientBean {
private final PrototypeBean prototypeBean; // ClientBean 생성 시점에 주입
// prototypeBean 의존관계 주입
@Autowired // 생략O
ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
prototypeBean.addCount(); // 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");
}
}
}
- new AnnotationConfigApplicationContext(..) 파라미터에 넘긴 ClientBean.class, PrototypeBean.class 는 자동 빈 등록
- 스코프 설정을 따로 하지 않으면, 기본 싱글톤으로 관리되기 때문에, @Scope("singleton") 생략O
- 생성자가 1개이면, @Autowired 생략O
5) 실행 결과
여기서는 prototypeBean 조회를 2번한 게 아니라 clientBean 조회를 2번했기 때문에 prototypeBean 생성, 초기화 메서드 호출이 1번만 일어난 것을 확인할 수 있다.
스프링은 일반적으로 싱글톤 빈을 사용하므로 싱글톤 빈(clientBean)이 프로토타입 빈(prototypeBean)을 사용하게 된다. 그런데, 싱글톤 빈은 생섬 시점에만 의존관계 주입을 받는다. 즉, 싱글톤 빈(clientBean) 생성시점에 프로토타입 빈(prototypeBean)이 생성되어 의존관계 주입이 되기 때문에, 프로토타입 빈을 사용(logic() 호출)할 때마다 새로운 프로토타입 빈이 생성되는 것은 아니다!
<정리>
싱글톤 빈이 프로토타입 빈을 사용하는 경우, 싱글톤 빈 생성시점에 의존관계 주입된 프로토타입 빈이 싱글톤 빈과 함께 계속 유지되는 것이 문제다.
프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때마다 새로 생성해서 사용는 것을 원한다. 하지만, 실무에서 프로토타입 빈을 사용할 일이 거의 없다.
(참고) 여러 빈에서 같은 프로토타입 빈을 주입 받으면, 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다. 물론 이 경우에도 프로토타입 빈을 사용할 때마다 새로 생성되는 것은 아니다.
clientA → prototypeBean@x01
clientB → prototypeBean@x02
9-3. 프로토타입 스코프(싱글톤 빈과 함께 사용시 문제점) 질문 정리
'Spring > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
[스프링 핵심 원리] 09. 빈 스코프 - 웹 스코프 & request 스코프 예제 만들기 (0) | 2022.05.24 |
---|---|
[스프링 핵심 원리] 09. 빈 스코프 - 프로토타입 스코프(싱글톤 빈과 함께 사용시 Provider로 문제 해결) (0) | 2022.05.24 |
[스프링 핵심 원리] 9. 빈 스코프 - 빈 스코프란? & 프로토타입 스코프 (0) | 2022.05.23 |
[스프링 핵심 원리] 08. 빈 생명주기 콜백 - 애노테이션 @PostConstruct, @PreDestroy (0) | 2022.05.20 |
[스프링 핵심 원리] 08. 빈 생명주기 콜백 - 빈 등록 초기화, 소멸 메서드 지정 (0) | 2022.05.20 |