-
Spring 기본 35 - 프로토타입 스코프 (싱글톤 빈과 함께 사용시 문제점)FrameWork/Spring&Spring-boot 2023. 11. 30. 09:56
프로토타입 스코프(싱글톤 빈과 함께 사용시 문제점)
스프링 컨테이너에 프로토타입 스코프빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다. 하지만 싱클톤 빈 과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의해야 한다.
먼저 스프링 컨테이너에 프로토타입 빈을 직접 요청하는 예제를 보자
프로토타입 빈 직접 요청
스프링 컨테이너에 프로토타입 빈 직접 요청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이 된다.
SingetonWithPrototypeTest1
package com.hello.core.scope; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; 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.Assertions.*; 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"); } } }
싱글톤 빈에서 프로토타입 빈 사용
이번에는 `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를 증가한다. 원래 1이었으므로 2가 된다.
SingletonWitjPrototypeTest1
package com.hello.core.scope; import lombok.RequiredArgsConstructor; 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; import static org.assertj.core.api.Assertions.*; 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); } @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; 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"); } } }
스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그런데 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 비이 새로 생성되디는 하지만, 싱글톤 빈과 함께 유지되는 것이다.
🤔아마 원하는 것이 이런 것을 아닐 수 있다. 프로토타입 빈을 주입시점에만 새로 생서하는게 아니라, 사용할 때 마다 새로 생성해서 사용하는 것을 원할 수도 있다.
➕참고
여러 빈에서 같은 프로토타입 빈을 주입 받으면, 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다. 예를 들어서 ClientA, ClientB가 각각 의존관계 주입을 받으면 각가 다른 인스턴스 프로토타입 빈을 주입 받는다.
clientA → prototypeBean@x01
clientB → prototypeBean@x02
물론 사용할 때 마다 새로 생성되는 것은 아니다.[출처 - 스프링 핵심 원리 - 기본편]
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢
www.inflearn.com
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring 기본 37 - 웹 스코프(request스코프 예제 만들기) (1) 2023.11.30 Spring 기본 36 - 프로토타입 스코프(싱글톤 빈과 함께 사용시 Provider로 문제 해결) (0) 2023.11.30 Spring 기본 34 - 프로토타입 스코프 (1) 2023.11.29 Spring 기본 33 - 빈 스코프 (0) 2023.11.29 Spring 기본 32 - 애노테이션 @PostConstruct,@PreDestory (1) 2023.11.29