🧐강의🧐
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/dashboard
✨ 문제 상황
- 스프링 컨테이너에 프로토타입 스코프 빈 요청 시, 항상 새로운 객체 인스턴스 생성 후 반환
- 싱글톤 빈과 함께 사용 시 문제 발생 가능
case1) (정상 동작) 프로토타입 스코프 빈만 직접 요청할 경우
🅰️ 클라이언트 A 가 스프링 컨테이너에 프로토타입 빈 요청 시 새로운 객체 생성 (x01)
→ addCount() 메서드 호출 시 count 값 1 증가
→ x01 의 count 값 : 1
🅱️ 클라이언트 B 가 스프링 컨테이너에 프로토타입 빈 요청 시 새로운 객체 생성 (x02)
→ addCount() 메서드 호출 시 count 값 1 증가
→ x02 의 count 값 : 1
case2) (문제 상황) 싱글톤 빈에서 프로토타입 빈을 주입받아 사용할 경우
🅰️ 클라이언트 A 가 clientBean.logic() 호출 시 프로토타입 빈의 addCount() 메서드 실행
→ count 값 : 1
🅱️ 클라이언트 B 가 clientBean.logic() 호출 시 프로토타입 빈의 addCount() 메서드 실행
→ clientBean 이 싱글톤이므로 클라이언트 A 가 호출했던 객체와 같은 객체가 반환됨
→ count 값 : 2
✨ 해결 방법
✅ 싱글톤 빈과 프로토타입 빈 함께 사용 시, 항상 새로운 프로토타입 빈을 생성할 수 있는 방법 필요
1️⃣ 스프링 컨테이너에 요청하기
- 싱글톤 빈에서 프로토타입 빈을 요청받을 때마다, 스프링 컨테이너에 새로 요청
- 가장 간단한 방법
· scope/SingletonWithPrototypeTest1
public class SingletonWithPrototypeTest1 {
...
// 싱글톤에서 프로토타입 빈 사용 확인
@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(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"); }
}
// 싱글톤에서 프로토타입 빈 사용
@Scope("singleton")
static class ClientBean {
@Autowired ApplicationContext applicationContext;
public int logic() {
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
}
✔️ DL (Dependency Lookup) 의존 관계 조회/탐색 : 직접 필요한 의존 관계 탐색 (cf. DI : 외부에서 의존 관계 주입)
✔️ 스프링 애플리케이션 컨텍스트 전체 주입받을 시, 컨테이너에 종속적인 코드가 되고, 단위 테스트가 어려워 짐
➡️ 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 기능 (DL 기능) 을 제공하는 무언가가 필요하다!
2️⃣ ObjectProvider 사용하기
- ObjectProvider : 지정한 빈을 컨테이너에서 대신 찾아주는 기능 제공 (DL 서비스)
- ObjectProvider : ObjectFactory + 편의 기능
- 별도의 라이브러리 필요 X, 스프링 의존
· scope/SingletonWithPrototypeTest1
public class SingletonWithPrototypeTest1 {
...
// 싱글톤에서 프로토타입 빈 사용
@Scope("singleton")
static class ClientBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
}
✔️ 스프링이 제공하는 기능을 사용하지만, 기능이 단순해 단위테스트나 mock 코드 만들기 편리
➡️ ObjectProvider 는 지금 필요한 정도의 DL 기능만 제공한다!
3️⃣ JSR-330 Provider 사용하기
- javax.inject.Provider 자바 표준 (스프링 부트 3.0 : jakarta.inject.Provider)
- get() 메서드 하나로 단순한 기능 구현 가능
- 별도의 라이브러리 필요
- 자바 표준 → 스프링이 아닌 다른 컨테이너에서도 사용 가능
· build.gradle 에 라이브러리 추가 & gradle refresh
...
dependencies {
...
implementation 'jakarta.inject:jakarta.inject-api:2.0.1' // JSR-330 Provider
}
...
· scope/SingletonWithPrototypeTest1
public class SingletonWithPrototypeTest1 {
...
// 싱글톤에서 프로토타입 빈 사용
@Scope("singleton")
static class ClientBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public int logic() { ... }
}
}
✔️ 자바 표준 & 단순한 기능으로 단위테스트나 mock 코드 만들기 쉬움
➡️ Provider 는 지금 필요한 정도의 DL 기능만을 제공한다!
참고
- ObjectProvider, JSR-330 Provider 등은 프로토타입 뿐만이 아니라 DL 이 필요한 경우라면 언제든지 사용 가능
- ObjectProvider : DL 을 위한 편의 기능 제공, 스프링 외 별도 라이브러리 필요 없음
- JSR-330 Provider : 코드를 스프링이 아닌 다른 컨테이너에서 사용해야 할 경우 사용
- 특별히 다른 컨테이너를 사용할 일이 없다면, 스프링이 제공하는 기능 사용하기
'Spring > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
[빈 스코프] 웹 스코프에 ObjectProvider 사용 시 발생하는 문제, CGLIB 가짜 프록시로 해결하기 (0) | 2024.01.08 |
---|---|
[빈 스코프] 웹 스코프 request 예제 만들기 (0) | 2024.01.03 |
[빈 스코프] 싱글톤 스코프 빈 & 프로토타입 스코프 빈 조회하기 (0) | 2024.01.02 |
[빈 스코프] 스코프 종류(싱글톤, 프로토타입, 웹) & 지정 방법 (0) | 2024.01.01 |
[빈 생명주기 콜백] 스프링 빈 생명주기 콜백 지원하는 방법 3가지 - 인터페이스, 설정 정보, 애노테이션 (0) | 2023.12.29 |