1. 빈 스코프란?
- 스프링 빈은 기본적으로 싱글톤 스코프로 생성되어 스프링 컨테이너가 시작할 때 생성되어 종료될 때까지 유지된다.
- 싱글톤: 디폴트 스코프로, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존 관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
- 웹 관련 스코프
1) request: 웹 요청이 들어오고 나갈 때까지 유지되는 스코프
2) session: 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
3) application: 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프
[ 컴포넌트 스캔 등록 ]
@Scope("prototype")
@Component
public class HelloBean {}
@Scope("prototype")
@Bean
PrototypeBean HelloBean() { return new HelloBean(); }
2. 프로토타입 스코프
2-1. 싱글톤 타입 빈 요청
- 싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환
2-2. 프로토타입 빈 요청
- 프로토타입 빈 요청
- 스프링DI 컨테이너에서 새로운 빈 생성 + 의존성 주입
- 생성된 빈 반환 -> 관리X
- 같은 요청이 오면
항상 새로운 프로토타입 빈을 생성해서 반환
- 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리함
- @PreDestroy 같은 종료 메서드가 호출되지 않음
- 종료 메서드는 클라이언트가 직접 해야함
2-3. 싱글톤 빈에서 프로토타입 빈 사용시 문제점
- 싱글톤 빈(clientBean)은 스프링 컨테이너 생성 시점에 함께 생성되고, 의존 관계 주입도 발생
- clientBean은 의존관계 자동주입 사용하므로 주입 시점에 스프링 컨테이너에 PrototypeBean을
요청
- 스프링 컨테이너는 PrototypeBean을 생성해서 clientBean에 반환
- PrototypeBean은 clientBean의 내부 필드에 참조값 보관
- clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다
- 클라이언트A가 clientBean.logic()을 호출하면 count = 1, 클라이언트B가 clientBean.logic()을 호출하면 count = 2가 나옴
- 내가 원하는 것: 클라이언트마다 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성되는 것을 원함
2-4. Provider로 문제 해결
- 프로토타입 빈을 사용할 때마다 스프링 컨테이너에 새로 요청
ObjectProvider
: 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것
- Provider보다 우선적으로 사용(스프링이 아닌 다른컨테이너에서도 사용해야한다면 Provider 사용)
- ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환(DL)
- 스프링에 의존적 + 이외의 기능 제공
public class SingletonBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
Provider
: 자바 표준
- provider의 get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환 (DL)
- 별도의 라이브러리가 필요
- 자바 표준으로 다른 컨테이너에서도 사용 가능
public class SingletonBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanObjectProvider;
public int logic() {
PrototypeBean object = prototypeBeanObjectProvider.get();
object.addCount();
return object.getCount();
}
}
3. 웹 스코프
3-1. 웹 스코프란?
- 웹 환경에서만 동작하는 스코프
- 스프링이 해당 스코프의 종료 시점까지 관리하여 종료 메서드가 호출됨
3-2. 웹 소코프 종류
- request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프로, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리됨
- session: HTTP Session과 동일한 생명주기를 가지는 스코프
- application: ServletContext와 동일한 생명주기를 가지는 스코프
- websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
3-3. requset 스코프 예제
@Component
@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);
}
}
- 로그를 출력하기 위한 클래스
- 이 빈은 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸됨
- 이 빈이 생성되는 시점에 자동으로 초기화 메서드를 사용해서 uuid 저장
- 이 빈은 HTTP 요청 당 하나씩 생성되므로, 다른 HTTP 요청과 구분할 때 uuid 사용
- 이 빈이 소멸되는 시점에 소멸 전 메서드로 종료 메시지 남김
- requestURL 은 이 빈이 생성되는 시점에는 알 수 없기 때문에 setter로 입력받음
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
MyLogger myLogger = myLoggerObjectProvider.getObject();
String requestURL = request.getRequestURL().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("test id");
return "OK";
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.log("service id = " + id);
}
}
[ 과정 ]
- MyLogger myLogger = myLoggerProvider.getObject();에서 처음만들어짐
- 그때, init()이 호출되면서 uuid를 request랑 연결
- setrequestURL 담아놓고 로그찍기
- 로그찍는 순간에는 이미 uuid, requestURL있기 때문에 에러없이 찍힘
수많은 요청이 와도 요청마다 따로 객체 관리
- 스프링 애플리케이션을 실행하는 시점에 싱글톤 빈은 생성해서 주입이 가능하지만, request 스코프 빈은 아직 생성되지 않기 때문에 MyLogger 빈이 아직 만들어지기 전이다.
- 이를 해결하기 위해 Provider를 사용해서 request 스코프 빈의 생성을 지연
4. 스코프와 프록시
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {}
- 프록시 방식으로, MyLogger의 가짜 프록시 클래스를 만들어두고, HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해둘 수 있음
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final MyLogger myLogger;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURI().toString();
System.out.println("myLogger = " + myLogger.getClass());
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id: " + id);
}
}