티스토리 뷰
스코프: 어떠한 빈 객체의 스코프는 해당 빈이 생성되고 유지되는 기간을 의미한다. 즉 빈이 존재할 수 있는 범위를 뜻함.
스프링이 제공 하는 주요 스코프
1. 싱글톤
2. 프로토타입
3. 웹 관련 스코프
-request, session, application
1. 싱글톤 - 기본 스코프로, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프.
2. 프로토타입
- 프로토타입 빈의 생성과 의존 관계 주입, 초기화까지 관여하고 더는 관리하지 않는다.
- 또한 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.
- 프로토타입 빈은 이를 조회한 클라이언트가 관리해야 한다. @PreDestroy 같은 종료 메소드 호출도 마찬가지.
- 싱글톤과 함께 사용할 경우 문제가 생길 수 있다. 예를 들어 개발자가 프로토타입을 의도한다면 그건 해당 빈이 컨테이너에서 새로운 인스턴스로 계속 만들어지길 원하는 경우일 것이다. 근데 만약 싱글톤 빈 안에 필드로 프로토타입 빈이 들어가 있고 프로토타입의 필드를 클라이언트가 필요로 한다면, 싱글톤 빈은 딱하나 있고 그 안에 있는 필드(프로토타입 필드)도 같은 필드이기 때문에 의도한 바와 다르다.
이런 문제는 몇 가지 방법으로 해결 가능하다.
1) 싱글톤 빈이 프로토타입을 사용할 때마다 컨테이너를 호출하는 것이다. 즉, 클라이언트의 필드에 스프링 컨테이너를 의존 관계로 주입한다.
2) ObjectProvider 사용
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 singletonClientUserPrototype() {
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(1);
}
@Scope("singleton")
static class ClientBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@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");
}
}
}
위 코드와 같이 prototypeBeanProvider.getObject() 을 통해 새로운 프로토타입 빈이 생성될 수 있다. 스프링에 의존적인 방법이다.
3) JSR330 Provider 사용
javax.inject(스프링부트3.0 이상은 jakarta) 라이브러리에서 Provider 를 사용하는 방법이다. 자바표준이며, 라이브러리 설치가 필요하다.
@Scope("singleton")
static class ClientBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
실무에서는 프로토타입을 사용하는 경우가 거의 없다고 한다.
3. 웹 스코프
- 웹 환경에서만 동작
- 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출된다.
- 웹 스코프의 종류
1) request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴 스가 생성되고, 관리된다.
2) session: HTTP Session과 동일한 생명주기를 가지는 스코프
3) application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
4) websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
- request 스코프
HTTP request가 들어왔을 때, 예를 들면 사용자의 uuid나 접속 url정보가 담긴 로그를 찍고 싶어서 MyLogger라는 클래스를 만들었다고 해보자. 로그 출력을 위한 클래스는 다음과 같다.
package hello.core.common;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;
//로그를 출력하기 위한 클래스
@Component
//이 빈은 HTTP 요청 당 하나씩 생성되고 HTTP 요청이 끝나는 시점에 소멸된다
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
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: " + this);
}
@PreDestroy
public void close() {
System.out.println("[" + uuid + "] request scope bean close: " + this);
}
}
@Scope(value = "request")로 지정하면, HTTP 요청 하나 당 하나의 request scope빈(위에서는 MyLogger)이 생성되고, HTTP 요청이 끝나는 시점에 소멸된다. 클라이언트들은 각자 다른 request scope 빈과 관련되기 때문에 정보를 구별하기가 쉽다.
로그 컨트롤러와 로그 서비스 계층에 MyLogger 클래스를 주입해 로그를 출력한다. 참고로 파라미터 값으로 넘겨서 해당 정보를 출력할 수도 있겠지만, 파라미터가 많아져서 지저분해지고 웹 관련 정보가 서비스 계층까지 넘어가면 웹 기술에 종속 될 수 있기 때문에 지양해야 한다.
일반적으로 의존 관계를 주입하고 서버를 실행하면, 오류가 일어난다. 왜냐하면 HTTP request가 들어와야 request scope 빈이 생성되는데 서버가 실행되는 시점에는 그런 요청은 없기 때문이다. 즉 내가 원하는 시점에 빈 생성을 할 수 있어야 문제를 해결 할 수 있다.
해결 방법으론 1. Provider / 2. 프록시 방식이 있는데, Provider의 코드를 간략하고 편리하게 줄인것이 프록시 방식이다.
방법은 단순하게 위 코드에서처럼
proxyMode = ScopedProxyMode.TARGET_CLASS
를 붙여주면 된다. 여기선 적용대상이 클래스라 TARGET_CLASS를 붙였다.
위 MyLogger를 조회해 보면 CGLIB로 만든 가짜 프록시 객체가 조회된다. 진짜 객체를 상속받은 가짜 객체가 스프링 컨테이너에 등록되어 실제 요청이 오면 내부적으로 실제 빈을 요청한다. 가짜 객체이기 때문에 서버 실행 시점에 request는 필요 없다. 의존 관계 주입 또한 이 가짜 객체로 이루어진다.
참고: 김영한-스프링 핵심 기본 원리(인프런)
'Spring' 카테고리의 다른 글
| 빈 생명주기와 콜백 (0) | 2023.08.17 |
|---|---|
| 롬북(Lombok)의 @RequiredArgsConstructor 기능 (0) | 2023.08.16 |
| 컴포넌트 스캔 (0) | 2023.08.15 |
| @Configuration의 역할과 싱글톤 (0) | 2023.08.14 |
| 싱글톤 패턴과 스프링 컨테이너 (0) | 2023.08.14 |