스코프(scope)는 빈이 생성되고 유지되는 범위를 뜻함.
스코프 | 생성 시점 | 유지 기간 | 특징 |
---|---|---|---|
Singleton | 컨테이너 시작 시 | 컨테이너 종료 시 | 스프링 기본 스코프, 인스턴스 1개 유지 |
Prototype | 요청할 때마다 | 반환 즉시 관리 종료 | 새로운 객체 반환, 컨테이너가 관리 안 함 |
Request | 웹 요청 시작 시 | 요청 종료 시 | 웹 요청마다 별도 객체 생성 |
Session | 웹 세션 시작 시 | 세션 종료 시 | 세션 동안 같은 객체 유지 |
Application | 애플리케이션 시작 시 | 애플리케이션 종료 시 | 서블릿 컨텍스트와 동일한 라이프사이클 |
@Component
@Scope("prototype")
public class HelloBean {}
@Bean
@Scope("prototype")
public PrototypeBean helloBean() {
return new HelloBean();
}
싱글톤 빈을 스프링 컨테이너에서 요청하면 항상 같은 인스턴스를 반환함.
싱글톤 빈 요청 과정
1. 스프링 컨테이너에 싱글톤 스코프의 빈을 요청.
2. 컨테이너는 해당 빈을 생성(최초 1회)하고 이후 요청에도 같은 인스턴스를 반환.
테스트 코드
public class SingletonTest {
@Test
void singletonScopeTest() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonBean.class);
SingletonBean bean1 = context.getBean(SingletonBean.class);
SingletonBean bean2 = context.getBean(SingletonBean.class);
assertThat(bean1).isSameAs(bean2); // 같은 인스턴스인지 확인
context.close();
}
@Component
@Scope("singleton") // 기본값이라 생략 가능
static class SingletonBean {
@PostConstruct
public void init() {
System.out.println("SingletonBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("SingletonBean.destroy");
}
}
}
출력 결과
SingletonBean.init
SingletonBean.destroy
→ 싱글톤 빈은 컨테이너 종료 시점까지 유지됨.
프로토타입 빈을 컨테이너에서 요청하면 매번 새로운 인스턴스를 반환함.
프로토타입 빈 요청 과정
1. 컨테이너에 프로토타입 빈 요청.
2. 요청이 올 때마다 새로운 인스턴스를 생성하여 반환.
3. 반환된 빈은 클라이언트가 직접 관리해야 함.
테스트 코드
public class PrototypeTest {
@Test
void prototypeScopeTest() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
assertThat(bean1).isNotSameAs(bean2); // 다른 인스턴스인지 확인
context.close();
}
@Component
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
출력 결과
PrototypeBean.init
PrototypeBean.init
→ 두 개의 요청에 대해 각각 새로운 인스턴스를 생성했음.
→ @PreDestroy
가 호출되지 않음(컨테이너가 관리하지 않기 때문).
싱글톤 빈이 의존관계 주입(DI) 으로 프로토타입 빈을 받으면 초기 주입된 프로토타입 빈을 계속 재사용함.
문제 코드
@Component
public class SingletonClient {
private final PrototypeBean prototypeBean;
@Autowired
public SingletonClient(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
테스트 코드
@Test
void singletonUsesSamePrototypeInstance() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SingletonClient.class, PrototypeBean.class);
SingletonClient client1 = context.getBean(SingletonClient.class);
SingletonClient client2 = context.getBean(SingletonClient.class);
int count1 = client1.logic(); // 1 증가
int count2 = client2.logic(); // 기존 프로토타입 객체를 사용하여 2 증가
assertThat(count1).isEqualTo(1);
assertThat(count2).isEqualTo(2);
context.close();
}
출력
PrototypeBean.init
1
2
→ 프로토타입 빈이 새로운 객체로 생성되지 않고, 기존 객체를 계속 사용함.
ObjectProvider
사용스프링 컨테이너에서 프로토타입 빈을 필요할 때마다 새롭게 가져오도록 설정.
@Component
public class SingletonClient {
private final ObjectProvider<PrototypeBean> prototypeBeanProvider;
@Autowired
public SingletonClient(ObjectProvider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
javax.inject.Provider
사용@Component
public class SingletonClient {
private final Provider<PrototypeBean> prototypeBeanProvider;
@Autowired
public SingletonClient(Provider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
웹 환경에서는 Request, Session, Application 스코프를 활용할 수 있음.
@Component
@Scope(value = "request")
public class MyLogger {
private String uuid;
private String requestURL;
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public void log(String message) {
System.out.println("[" + uuid + "][" + requestURL + "] " + message);
}
@PostConstruct
public void init() {
uuid = UUID.randomUUID().toString();
System.out.println("[" + uuid + "] request scope bean create");
}
@PreDestroy
public void close() {
System.out.println("[" + uuid + "] request scope bean close");
}
}
ObjectProvider
또는 Provider
사용.@Scope("request")
등으로 활용 가능.