
스프링 빈은 스프링 컨테이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때까지 유지된다. 이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다
스코프(Scope)란? : 빈이 존재할 수 있는 범위
✅ 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
✅ 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 고나여하고 더는 관리하지 않음
✅ 웹 관련 스코프
request : 웹 요청에서 들어오고 나갈 때까지 유지
session : 웹 세션이 생성되고 종료될 때까지 유지
appliccation : 서블릿 컨텍스트와 같은 범위로 유지
websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프
@Scope("prototype")
@Component
static class Prototype{}
@Scope("prototype")
@Bean
Prototype HelloBean(){
return new HelloBean();
}


새로운 빈을 생성해서 반환하고, 관리하지 않는다.
싱글톤 스코프 처럼 같은 인스턴스를 반환하는 것이 아니라 요청마다 새로운 빈을 생성해서 반환하게 된다.
코드로 알아보자
package com.scope;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class PrototypeTest {
@Test
void prototypeTest() {
// 스프링 컨테이너로 등록
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Prototype.class);
// bean1 인스턴스 생성
Prototype bean1 = ac.getBean(Prototype.class);
System.out.println(bean1);
// bean2 인스턴스 생성
Prototype bean2 = ac.getBean(Prototype.class);
System.out.println(bean2);
assertThat(bean1).isNotSameAs(bean2);
// 소멸 (호출되지 않음)
ac.close();
}
// 프로토타입 스코프로 지정
@Scope("prototype")
static class Prototype{
// 초기화
@PostConstruct
public void init() {
System.out.println("PrototypeInit");
}
// 소멸
@PreDestroy
public void destory() {
System.out.println("PrototypeDestory");
}
}
}
PrototypeInit
com.scope.PrototypeTest$Prototype@460ebd80
PrototypeInit
com.scope.PrototypeTest$Prototype@6f3c660a
다른 인스턴스로 호출됨. 소멸 메서드가 실행되지 않음 why? 프로토타입의 스코프는 생성과 초기화까지만 관리하고 소멸까지 관리하지 않는다.

package com.scope;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class SingleTonTest {
@Test
void singleTonTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingleTon.class);
// bean1 인스턴스 생성
SingleTon bean1 = ac.getBean(SingleTon.class);
System.out.println(bean1);
// bean2 인스턴스 생성
SingleTon bean2 = ac.getBean(SingleTon.class);
System.out.println(bean2);
// 같은 인스턴스인가
assertThat(bean1).isSameAs(bean2);
// 소멸
ac.close();
}
@Scope("singleton")
static class SingleTon{
@PostConstruct
public void init() {
System.out.println("SingletonInit");
}
@PreDestroy
public void destory() {
System.out.println("SingletonDestory");
}
}
}
SingletonInit
com.scope.SingleTonTest$SingleTon@460ebd80
com.scope.SingleTonTest$SingleTon@460ebd80
SingletonDestory
종료메서드까지 정상 호출 되었다. 싱글톤 스코프의 경우
생성->초기화->소멸까지 관리한다.
package com.scope;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class SingleTonWithPrototypeTest {
@Test
void PrototypeTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Prototype1.class);
// 프로토스코프 타입의 인스턴스 bean1 생성
Prototype1 bean1 = ac.getBean(Prototype1.class);
// 카운트추가
bean1.addCount();
// 프로토스코프 타입의 인스턴스 bean2 생성
Prototype1 bean2 = ac.getBean(Prototype1.class);
// 카운트 추가
bean2.addCount();
// bean1에 저장된 카운트
System.out.println(bean1.getCount()); // 1
// bean2에 저장된 카운트
System.out.println(bean2.getCount()); // 1
assertThat(bean1.getCount()).isEqualTo(1);
assertThat(bean2.getCount()).isEqualTo(1);
ac.close(); // 출력되지 않음
}
@Scope("prototype")
static class Prototype1{
private int count=0;
// 초기화
@PostConstruct
public void init() {
System.out.println("PrototypeInit : " + this);
}
public void addCount() {
count++;
}
public int getCount() {
return count;
}
// 소멸
@PreDestroy
public void destory() {
System.out.println("PrototypeDestory");
}
}
}
PrototypeInit : com.scope.SingleTonWithPrototypeTest$Prototype1@21a21c64
1
PrototypeInit : com.scope.SingleTonWithPrototypeTest$Prototype1@6f3c660a
1
프로토스코프 타입의 빈은 생성 시마다 새로운 인스턴스를 반환해주기 때문에 카운트가 2가 아닌 1로 반환된다. 반환 후 소멸가지 관리하지 않기 때문에 소멸메소드는 출력되지 않는다.
DI(의존관계 주입)이 아닌 의존관계를 직접 찾는 DL(Dependency Lookup) 의존관계 조회(탐색)이라고 한다.
✅ ObjectFactory, ObjectProvider
✅ Provider 사용
ObjectFactory, ObjectProvider 사용package com.scope;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Provider;
public class SingleTonWithPrototypeTest2 {
@Test
@DisplayName("싱글톤에서 프로토타입 빈 사용하기")
void ClientBeanTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, Prototype1.class);
ClientBean bean1 = ac.getBean(ClientBean.class);
System.out.println(bean1.logic());
ClientBean bean2 = ac.getBean(ClientBean.class);
System.out.println(bean2.logic());
ac.close(); // 출력되지 않음
}
static class ClientBean{
private final ObjectProvider<Prototype1> prototypeProvider;
public ClientBean(ObjectProvider<Prototype1> prototypeProvider) {
this.prototypeProvider = prototypeProvider;
}
public int logic() {
// 항상 새로운 프로토타입 빈을 생성하고 반환한다.
Prototype1 prototypebean = prototypeProvider.getObject();
prototypebean.addCount();
return prototypebean.getCount();
}
}
@Scope("prototype")
static class Prototype1{
private int count=0;
@PostConstruct
public void init() {
System.out.println("PrototypeInit" + this);
}
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PreDestroy
public void destory() {
System.out.println("PrototypeDestory");
}
}
}
PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@f316aeb
1
PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@6aa3a905
1
ObjectProvider는ObjectFactory를 상속한다.ObjectFactory에서 편의기능이 추가된 것이ObjectProvider이다
Provider 사용package com.scope;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Provider;
public class SingleTonWithPrototypeTest2 {
@Test
@DisplayName("싱글톤에서 프로토타입 빈 사용하기")
void ClientBeanTest1() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean1.class, Prototype1.class);
ClientBean1 bean1 = ac.getBean(ClientBean1.class);
System.out.println(bean1.logic());
ClientBean1 bean2 = ac.getBean(ClientBean1.class);
System.out.println(bean2.logic());
ac.close(); // 출력되지 않음
}
static class ClientBean1{
private final Provider<Prototype1> providers;
public ClientBean1(Provider<Prototype1> providers) {
super();
this.providers = providers;
}
public int logic() {
Prototype1 prototypebean = providers.get();
prototypebean.addCount();
return prototypebean.getCount();
}
}
@Scope("prototype")
static class Prototype1{
private int count=0;
@PostConstruct
public void init() {
System.out.println("PrototypeInit" + this);
}
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PreDestroy
public void destory() {
System.out.println("PrototypeDestory");
}
}
}
PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@324a0017
1
PrototypeInitcom.scope.SingleTonWithPrototypeTest2$Prototype1@6187d1f5
1
testImplementation 'jakarta.inject:jakarta.inject-api:2.0.1'build.gradle에 추가 후 사용할 수 있다.
Provider는 자바 표준이므로, 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.