스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환하지만, 싱글톤 빈과 함께 사용하면 의도한 대로 잘 동작하지 않아 주의해야 한다.
1. 프로토타입 빈 직접 요청
스프링 컨테이너에 프로토타입 빈 직접 요청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 이 된다.
- src/test/java/hello/core/scope/SingletonWithPrototypeTest1.java
package hello.core.scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import static org.assertj.core.api.Assertions.assertThat;
public class SingletonWithPrototypeTest1 {
@Test
void prototypeFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
assertThat(prototypeBean1.getCount()).isEqualTo(1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
assertThat(prototypeBean2.getCount()).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");
}
}
}
- 콘솔
PrototypeBean.init hello.core.scope.SingletonWithPrototypeTest1$PrototypeBean@19058533
PrototypeBean.init hello.core.scope.SingletonWithPrototypeTest1$PrototypeBean@52cb4f50
2. 싱글톤 빈에서 프로토타입 빈 사용
clientBean 이라는 싱글톤 빈이 의존관계 주입을 통해 프로토타입 빈을 주입받아 사용해본다.
싱글톤에서 프로토타입 빈 사용)

clientBean 은 싱글톤이므로, 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 됨
1) clientBean 은 의존관계 자동주입을 사용하는데, 주입 시점에 스프링 컨테이너에게 프로토타입 빈을 요청
2) 스프링 컨테이너는 프로토타입 빈을 생성하여 clientBean 에 반환
프로토타입 빈의 count 필드 값은 0
clientBean 은 프로토타입 빈을 내부 필드에 보관 (정확히는 참조값을 보관)

클라이언트A는 clientBean 을 스프링 컨테이너에 요청해서 받는다. 싱글톤이므로 항상 같은 clientBean 이 반환된다.
3) 클라이언트A는 clientBean.logic() 호출
4) clientBean 은 prototypeBean 의 addCount() 메서드를 호출하여 count 필드를 +1
따라서 프로토타입 빈(x01)의 count 필드 값은 1 이 된다.

클라이언트B는 clientBean 을 스프링 컨테이너에 요청해서 받는다. 싱글톤이므로 항상 같은 clientBean 이 반환된다.
clientBean 내부의 프로토타입 빈은 과거에 주입이 끝난 빈이다. 의존관계 주입 시점에 스프링 컨테이너에 의해 새로 생성되어 주입받은 것이다.
따라서 사용할 때마다 새로 생성되지 않는다.
5) 클라이언트B는 clientBean.logic() 호출
6) clientBean 은 prototypeBean 의 addCount() 메서드를 호출하여 count 필드를 +1
원래 count 필드 값이 1 이었으므로, 2 가 된다.
- src/test/java/hello/core/scope/SingletonWithPrototypeTest2.java
package hello.core.scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
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 static org.assertj.core.api.Assertions.*;
public class SingletonWithPrototypeTest2 {
@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
// ApplicationContext applicationContext; // 스프링에 의존적이라 좋은 코드 아님
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
// PrototypeBean prototypeBean = applicationContext.getBean(PrototypeBean.class);
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");
}
}
}
- 콘솔
PrototypeBean.init hello.core.scope.SingletonWithPrototypeTest2$PrototypeBean@3b46dd8
스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그런데 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제다.
프로토타입 빈을 주입 시점에만 새로 생성하는게 아닌, 사용할 때마다 새로 생성해서 사용하는 것을 원할 것이다.
* 참고
여러 싱글톤 빈에서 같은 프로토타입 빈을 주입받을 때, 각각 새로운 프로토타입 빈이 생성되어 주입된다.
예시)
싱글톤 빈 clientA, clientB 가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입 빈을 주입받는다.
clientA <- prototypeBean@x01
clientB <- prototypeBean@x02
당연히 사용할 때마다 새로 생성되진 않는다.
참고링크 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8?cid=325969
스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 49,598명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프
www.inflearn.com
'강의 실습 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 웹 스코프 (0) | 2026.05.09 |
|---|---|
| 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결 (0) | 2026.05.09 |
| 프로토타입 스코프 (0) | 2026.05.06 |
| 빈 스코프란? (0) | 2026.05.05 |
| 어노테이션 @PostConstruct, @PreDestroy (0) | 2026.05.04 |
댓글