스프링 빈은 스프링 빈 컨테이너의 시작과 함께 생성되어 종료 될 때 까지 유지되는 싱글톤 스코프로 생성된다.
Table of Content
빈 스코프 종류
스프링은 다양한 빈 스코프를 지원한다.
•
싱글톤 : 기본 스코프, 컨테이너 시작과 종료까지 유지되는 가장 넓은 범위
•
프로토타입 : 스프링 컨테이너가 프로토 타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위
•
웹 관련 스코프 :
◦
“request” : 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
◦
“session” : 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
◦
“application” : 웹 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
싱글톤 스코프
싱글톤은 항상 같은 인스턴스의 스프링 빈을 반환한다. 매번 요청마다 같은 인스턴스 주소값을 가진 객체를 반환한다.
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 SingletonScopeTest {
@Test
void singletonBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
System.out.println("singletonBean1 = " + singletonBean1);
System.out.println("singletonBean2 = " + singletonBean2);
Assertions.assertThat(singletonBean1).isSameAs(singletonBean2);
ac.close();
}
@Scope("singleton")
static class SingletonBean {
@PostConstruct
public void init() {
System.out.println("SingletonBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("SingletonBean.destroy");
}
}
}
Java
복사
프로토타입 스코프
하지만 프로토타입 스코프는 요청 시점에 프로토타입 빈을 생성하고 필요한 의존관계를 주입하고 빈을 반환한 후 더이상 관리하지 않고 새로운 요청에 또 새로운 빈을 생성한다.
따라서 “스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화 까지만 처리한다. 빈을 관리하는 책임은 해당 빈을 받은 클라이언트에 있다. 따라서 @PreDestroy 같은 종료 메서드가 호출되지 않는다.
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 PrototypeScopeTest {
@Test
void prototypeBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
System.out.println("PrototypeScopeTest.prototypeBeanFind 1");
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
System.out.println("PrototypeScopeTest.prototypeBeanFind 2");
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");
}
}
}
Java
복사
•
프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회(요청) 할 때 생성되고 초기화 메서드도 실행된다.
•
빈을 두번 조회 했으므로 다른 스프링 빈이 생성되며 초기화도 두번 실행 된 것을 확인 할 수 있다.
•
또한 스프링 컨테이너가 생성, 의존관계주입, 초기화 까지 관여하고 이후 관리하지 않기 때문에 @PreDestroy 메서드(close())가 실행되지 않은 것을 볼 수 있다.
•
종료 메서드는 직접 해야 한다. PrototypeBean.close();
싱글톤과 프로토타입 스코프를 함께 사용하는 경우
프로토타입은 호출 시점마다 생성되는 것 같지만 다음처럼 싱글톤 내부에서 프로토타입 빈을 생성하는 경우 싱글톤 빈과 생명주기를 함께하는 것을 볼 수 있다.
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.assertThat;
public class SingletonWithPrototypeTest1 {
@Test
void prototypeFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
assertThat(prototypeBean1.getCount()).isEqualTo(1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
assertThat(prototypeBean2.getCount()).isEqualTo(1);
}
@Test
void singgletonClientUseProtorype() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class, ClientBean.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);
}
@Scope("singleton")
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 " + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy " + this);
}
}
}
Java
복사
스프링은 일반적으로 싱글톤 빈을 사용하기 때문에 이렇게 작동하면 프로토타입 스코프를 이용하는 의미가 없어진다.
Provider로 위 문제 해결
의존관계를 외부에서 주입(DI)받는 것이 아니라 의존관계를 찾는 것을 Dependency Lookup(DL)이라 한다. 지금 필요한 기능은 프로토타입 빈을 컨테이너에서 찾아주는 DL 기능만 제공하는 것이 필요하다.
ObjectFactory, ObjectProvider
package hello.core.scope;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
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.assertThat;
public class SingletonWithPrototypeTest1 {
@Test
void prototypeFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
assertThat(prototypeBean1.getCount()).isEqualTo(1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
assertThat(prototypeBean2.getCount()).isEqualTo(1);
}
@Test
void singgletonClientUseProtorype() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class, ClientBean.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("singleton")
static class ClientBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
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 " + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy " + this);
}
}
}
Java
복사
ObjectProvider.getObject()를 통해서 새로운 프로토타입 빈이 생성되는 것을 확인 할 수 있다.
•
getObject()는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(DL)
•
새로 요청하기 때문에 프로토타입의 새로운 빈이 생성 되어 원하는 프로토타입 빈이 생성되는 것이 확인
•
스프링 프레임워크에 의존적인 점이 있다.
JSR-330 자바 표준 라이브러리 사용
위 ObjectProvider는 스프링 프레임워크에 의존적인 문제가 있다. 자바 표준에서 Provider를 제공해주는 라이브러리를 활용하면 보다 범용성있게 사용(다른 컨테이너 사용) 할 수 있다.
build.gradle에서 Provider를 사용하기 위해 라이브러리를 주입해야 한다.
//JSR-330 자바 표준 Provider 사용하기
implementation 'javax.inject:javax.inject:1'
Java
복사
이후 코드에서 ObjectProvider가 아닌 Provider 인터페이스를 직접 사용 할 수 있다. 해당 인터페이스는 get()처럼 조회하는 메서드가 포함되어 있다.
...
@Scope("singleton")
static class ClientBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
...
Java
복사
실행해보면 provider.get()을 통해 항상 새로운 프로토타입 빈이 생성되는 것을 확인 할 수 있다.
프로토타입 빈 필요?
실제로 스프링 컨테이너의 싱글톤으로 대부분 완성하기 때문에 위처럼 요청마다 새로운 인스턴스를 생성하는 프로토타입의 필요성에 궁금증이 생긴다. 직접 사용할 상황은 많지 않지만 사용시마다 새로운 객체가 필요한 상황에 사용하면 된다.
다시 정리하면
•
싱글톤 : 스프링 컨테이너의 시작과 끝까지 함께하는 매우 긴 스코프
•
프로토타입 : 생성과 의존관계 주입, 초기화 까지 진행하는 특별한 스코프
Toggle H1
Toggle H2
Toggle H3
Related Posts
Search