1. Bean Scope란?
빈이 존재할 수 있는 범위
(1) 스프링이 지원하는 Scope 종류
- 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하는 않는 매우 짧은 범위의 스코프이다.
- 웹 관련 스코프
- request : 웹 요청이 들어오고 나갈 때까지 유지되는 스코프이다.
- session : 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
- application : 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프이다.
2. Singleton Scope
Singleton (싱글톤)
1. 순수한 DI 컨테이너 테스트 웹 애플리케이션에서 여러 고객이 동시에 요청을 보내는 경우 어떻게 반응할 것인가? 순수한 DI 컨테이너(AppConfig) 테스트 ackage hello.core.singleton; import hello.core.AppC..
choiyeonho903.tistory.com
3. Prototype Scope
프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환
- 스프링 컨테이너에 요청할 때 마다 새로 생성된다.
- 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여
- 종료 메서드가 호출되지 않는다.
- 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다. 종료 메서드에 대한 호출도 클라리언트가 직접 해야 한다.
(1) Prototype 빈 생성 과정
- 프로토타입 스코프의 빈을 스프링 컨테이너에게 요청한다.
- 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다.
- 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환한다.
- 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.
스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다.
클라이언트에게 빈을 반환하고, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다.
@PreDestroy 같은 종료 메서드가 호출되지 않는다.
(2) Prototype Bean 테스트
Prototype Scope Bean Test
package 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;
public class PrototypeTest {
@Test
public 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("PrototypeBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
Test코드 결과
- 프로토타입 빈을 2번 조회했을 때 완전히 다른 스프링 빈이 생성되고, 초기화도 2번 실행된 것을 확인할 수 있다.
- 프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화까지만 관여하고, 더는 관리하지 않는다. 따라서 스프링 컨테이너가 종료될 때 @PreDestory 종료 메서드 실행되지 않는다.
4. Prototype Scope - Singleton Scope 빈과 함께 사용 시 문제 발생
Prototype Scope - Singleton Scope 빈과 함께 사용 Test Code
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.Assertions.*;
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(2);
}
static class ClientBean {
private final PrototypeBean prototypeBean;
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
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");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
Test코드 진행 과정
- clientBean은 싱글톤이므로, 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다.
- clientBean은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청
- 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환. 프로토타입 빈의 count 필드 값은 0
- clientBean은 포로 토타 입 빈을 내부 필드에 보관한다.
- 클라이언트 A가 clientBean을 요청하면 항상 같은 clientBean이 반환된다. (싱글톤)
- 클라이언트 A가 clientBean.logic()을 호출한다.
- clientBean은 prototypeBean의 addCount()를 호출해서 포로토타입 빈의 count 값을 증가시켜서 1이 된다.
- 클라이언트 B는 clientBean을 요청하면 항상 같은 clientBean이 반환된다. (싱글톤)
- clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이다. 사용할 때마다 새로 생성되는 것이 아니다.
- 클라이언트 B는 clientBean.logic()을 호출한다.
- clientBean은 prototypeBean의 addCount()를 호출해서 포로 토타 입 빈의 count 증가시켜서 2가 된다.
5. 문제 해결
싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 어떻게 하면 사용할 때마다 항상 새로운 프로토타입 빈을 생성할 수 있을까?
(1) 스프링 컨테이너에 요청
싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청을 보낸다.
@Autowired
private ApplicationContext ac;
public int logic() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- 실행해보면 ac.getBean()을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
- 의존관계를 외부에서 주입(DI) 받는 게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존 관계 조회(탐색) 이라 한다.
- 단점
- 스프링의 애플리케이션 콘텍스트 전체를 주입받게 되면, 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다.
(2) ObjectProvider
- 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주고 DL 기능만 제공한다.
- ObjectFactory에 편의 기능을 추가해서 ObjectProvider가 만들어졌다.
@Autowired
private ObjectProvider<PrototypeBean> prototypeProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(DL)
- 스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기는 편하다.
(3) JSR-330 Provider
javax.inject.Provider라는 JSR-330 자바 표준을 사용한다.
//implementation 'javax.inject:javax.inject:1' gradle 추가 필수
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- provider.get()을 통해서 항상 새로운 프로토타입 빈을 생성되는 것을 확인할 수 있다.
- provider의 get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.
- 자바 표준이고, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기는 훨씬 쉽다.
- 특징
- get() 메서드 하나로 기능이 매우 단순
- 별도의 라이브러리가 필요
- 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
ObjectProvider는 DL을 위한 편의 기능을 많이 제공해주고 스프링 외에 별도의 의존관계 추가가 필요 없기 때문에 편리한다. 기본적으로는 ObjectProvider를 사용하자.
만약 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면(거의 일어나지 않는다) 자바 표준 JSR-330 Provider를 사용하자.
본 글은 김영한 님의 "스프링 핵심 원리"(인프런) 유료 강의를 들으며 요약, 정리하고 일부 정보를 추가 작성한 것입니다.
반응형
'Spring(JAVA Framework) > Spring Core' 카테고리의 다른 글
Bean Scope - 2 (Web Scope) (0) | 2021.08.20 |
---|---|
빈(Bean) 생명 주기 콜백 (0) | 2021.08.19 |
조회한 모든 빈이 가져오기 (List, Map) (0) | 2021.08.19 |
Lombok 라이브러리 (0) | 2021.08.19 |
@ Autowired 사용시 조회 빈이 중복되는 경우 (0) | 2021.08.19 |
댓글