스프링은 다음과 같이 다양한 스코프를 지원한다.
- 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
- 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다. (생성 ~ 초기화까지만 관여)
- 웹 관련 스코프
- Request : 웹 요청이 들어오고 나갈 때까지 유지되는 스코프이다.
- Session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프이다.
- Application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.
스프링 빈은 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때 까지 유지되므로 싱글톤 스코프이다. 스프링을 사용하면 대부분 싱글톤 스코프만 사용한다는데 가끔 프로토타입 스코프를 사용해야 하는 경우가 있다고 한다. 따라서 프로토타입 스코프에 대해서 조금 알아보자.
빈 스코프는 다음과 같이 명시해줄 수 있는데 default 값은 singleton 스코프이다.
@Scope("prototype") // default = "singleton"
프로토타입 스코프 빈을 스프링 컨테이너에서 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다. 이때 스프링 컨테이너는 프로토타입 빈의 생성, DI, 초기화까지만 처리하고 이후 프로토타입 빈의 관리 책임을 클라이언트에게 넘긴다. 따라서 @PreDestroy 같은 종료 메서드가 호출되지 않는다.
프로토타입 스코프 빈을 테스트해보자.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
public class PrototypeTest {
@Test
public void prototypeBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean01 = ac.getBean(PrototypeBean.class);
PrototypeBean prototypeBean02 = ac.getBean(PrototypeBean.class);
Assertions.assertThat(prototypeBean01).isNotSameAs(prototypeBean02);
}
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("초기화");
}
@PreDestroy
public void destroy() {
System.out.println("종료");
}
}
}
// 출력결과
초기화
초기화
출력 결과를 보면 알겠지만 @PreDestroy가 실행되지 않는다. 또한 테스트 결과를 통해서 프로토타입 스코프는 항상 다른 인스턴스를 반환한다는 것을 알 수 있다.
그런데 프로토타입 스코프를 싱글톤 스코프와 함께 사용하면 문제가 발생할 수 있다. 예를 들어 싱글톤 스코프 빈이 내부적으로 프로토타입 스코프 빈을 사용한다고 하자. 우리가 원하는 동작은 싱글톤 스코프 빈이 매번 새로운 프로토타입 스코프 빈을 사용하는 것이다. 다음 테스트 코드를 보자.
import lombok.RequiredArgsConstructor;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
public class SingletonWithPrototypeTest {
@Test
public void findPrototypeFromSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class, PrototypeBean.class);
SingletonBean singletonBean = ac.getBean(SingletonBean.class);
int count01 = singletonBean.logic();
int count02 = singletonBean.logic();
Assertions.assertThat(count01).isEqualTo(count02);
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
}
@RequiredArgsConstructor
static class SingletonBean {
private final PrototypeBean prototypeBean;
public int logic() {
prototypeBean.addCount();
int count = prototypeBean.getCount();
System.out.println("count = " + count);
return count;
}
}
}
그런데 위 테스트 코드는 테스트를 실패한다. count01은 1이고 count02는 2이기 때문에 테스트가 통과하지 않는다. 즉 프로토타입 스코프 빈이 새롭게 생성되지 않는다는 것을 의미한다. 왜냐하면 싱글톤 빈이 의존관계를 주입받을 때 스프링 컨테이너에 프로토타입 빈을 한 번 호출하고 그 이후에는 주입받은 객체를 계속 사용하기 때문에 같은 객체를 재사용하는 것이다.
그렇다면 싱글톤 스코프 빈에서 매번 새로운 프로토타입 스코프 빈을 사용하려면 어떻게 해야 할까?
방법은 간단한데.. 그냥 매번 새로운 프로토타입 스코프 빈을 스프링 컨테이너에 요청하면 된다. 또는 스프링의 ObjectProvider나 자바 표준(JSR-330)인 Provider를 사용해도 된다. 이렇게 직접 원하는 객체를 찾아서 주입받는 것을 DL(Dependency Lookup)이라고 한다.
- 스프링 컨테이너에 직접 요청하기 위해서 스프링의 어플리케이션 컨텍스트를 주입받는 방법은 스프링 컨테이너에 종속적인 방법이 되므로 단위 테스트가 어려워진다.
- ObjectProvider를 사용하면 단위 테스트가 쉬워지고 Mock 코드를 만들기도 쉬워지며, 다양한 편의 기능을 제공한다. 다만 스프링에 의존하게 된다. (다른 컨테이너에는 사용할 수 없음)
@RequiredArgsConstructor
static class SingletonBean {
private final ObjectProvider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
- 자바 표준 Provider는 별도의 라이브러리(javax.inject:javax.inject:1)가 필요하다. 이 또한 단위 테스트가 쉬워지고 Mock 코드를 만들기도 쉬워진다. 제공하는 기능이 get() 메서드 하나로 매우 단순하고 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
결론
가장 궁금했던 점 : 프로토타입 빈은 언제 사용할까? 프로토타입 빈은 실무에서도 잘 쓰이지 않고 싱글톤 빈으로 대부분의 문제를 해결할 수 있다고 한다.
또한 ObjectProvider나 자바 표준 Provider는 DL이 필요한 경우 언제든 사용할 수 있다고 한다.
스프링을 사용하다 보면 다양한 기능들이 자바 표준과 겹칠 때가 있다고 한다. 대부분 스프링이 더 다양하고 편리한 기능을 제공하기 때문에 스프링이 제공하는 기능을 사용하면 되고, 만약 스프링에서 자바 표준을 권장한다면 자바 표준을 사용하면 된다고 한다.
reference
https://www.inflearn.com/course/스프링-핵심-원리-기본편/
'Spring' 카테고리의 다른 글
MockMvc 테스트 시 한글 깨짐 (0) | 2022.05.21 |
---|---|
웹 스코프 (0) | 2022.04.30 |
자동, 수동의 올바른 실무 운영 기준 (0) | 2022.04.08 |
같은 클래스 빈이 여러 개일 때 (0) | 2022.04.07 |
nginx - 리버스 프록시로 스프링 CORS 문제 해결하기 (0) | 2022.04.07 |