스프링의 request 스코프란?
스프링에는 웹 스코프, 싱글톤 스코프, 프로토타입 스코프 크게 3가지가 있다.
그 중 웹 스코프의 request 스코프를 알아 보자.
Request 스코프란
웹 스코프는 3가지로 나뉜다.
request
: 웹 요청이 들어오고 나갈 때 까지- session : 웹 세션이 생성되고 종료될 때 까지
- applicaiont : 웹의 서블릿 컨텍스트와 같은 범위 ???????
reqest 스코프는 http의 요청이 오고 응답해서 나갈 때 까지 스프링 빈에 관리되는 스코프이다.
싱글톤과는 다르게 http 요청 마다 개별로 빈을 생성해준다.
언제 사용할 수 있을까?
여러가지가 있겠지만 사용자별 로그에 사용될 수 있다.
10명의 사용자가 요청을 했는데 로그가 어떤 사용자인지 구분없이 보인다면 로그를 보기가 힘들 것이다.
http 요청 마다 새로운 빈을 생성하는 reqeust 스코프를 이용해 uuid 별로 요청을 구분하도록 사용해 볼 수 있다.
Provider를 이용하기
로그 클래스
스코프를 request로 설정하고, uuid를 초기화 시점에 주입한다.
uuid는 요청을 구분하기 위해 사용된다.
@Scope(value = "request")
@Component
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);
}
}
컨트롤러 클래스
- request 스코프는 http 요청이 들어와야 초기화가 된다. 그렇기 때문에
스프링 컨테이너가 뜰 때 의존성 주입을 받을 수가 없다. 요청 자체가 없기 때문에 빈이 없기 때문이다.
이럴 때 활용할 수 있는 것이Provider
이다. Provider를 이용해빈 생성은 지연시켜 .getObject()시점
에
초기화를 하는 방법이다. - 정말 요청별로 빈이 생성되어, 응답이 끝날 때 까지 남아있는지 확인하기 위해 딜레이 1초를 주었다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final ObjectProvider<MyLogger> myLoggerProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) throws InterruptedException {
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.setRequestUrl(requestURL);
Thread.sleep(1000);
myLogger.log("controller test");
return "OK";
}
}
결과
로그를 보면 uuid가 c49a4877-e061-4919-b7d2-19cc76868be8, ee1af249-12f3-4aa5-9af2-b940c595b998 두 개가 있다.
이는 요청을 두 번 했기 때문에 각각 생긴 것이다. 객체도 @5385447b, @57dcc587 이렇게 요청별로 생성되었다.
[c49a4877-e061-4919-b7d2-19cc76868be8] request scope bean create : hello.core.common.MyLogger@5385447b
[ee1af249-12f3-4aa5-9af2-b940c595b998] request scope bean create : hello.core.common.MyLogger@57dcc587
[c49a4877-e061-4919-b7d2-19cc76868be8][http://localhost:8080/log-demo]controller test
[c49a4877-e061-4919-b7d2-19cc76868be8] request scope bean close : hello.core.common.MyLogger@5385447b
[ee1af249-12f3-4aa5-9af2-b940c595b998][http://localhost:8080/log-demo]controller test
[ee1af249-12f3-4aa5-9af2-b940c595b998] request scope bean close : hello.core.common.MyLogger@57dcc587
ScopedProxyMode.TARGET_CLASS 를 이용하기
Provider 보다 더 간단한 방법이 있다. ScopedProxyMode.TARGET_CLASS
옵션이다.
옵션을 주면 마치 싱글톤 처럼 사용을 할 수 있다.
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class MyLogger {...}
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final MyLogger myLogger;
...
}
어떻게 이게 가능할까?
프록시에 의해서 가능하다. 프록시는 가짜 객체를 말 하는데 여기서 MyLogger는 CGLIB을 이용해
가짜 객체인 상태이다. 껍데기일 뿐이고, http요청이 올 때 까지 빈 생성을 지연 시키기 위해 가짜 객체라도 주입 해놓은 것이다.
실제 요청이 들어오면 그 때 주입을 하게 된다.
정리 하자면
- CGLIB으로 실제 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.(지연 하는 것이다)
- 프록시 객체는 실제 요청이 오면 그 때 내부에서 실제 빈을 달라고 컨테이너에 요청한다.
- 프록시는 request 스코프가 아닌 그냥 가짜 객체이다.
로그를 확인해 보면 프록시 객체라는 것을 확인할 수 있다.
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$92628da2
주의 사항
- 싱글톤 같기 때문에 잘못 인식하고 사용할 가능성이 있다.
이런 특별한 상황은 최대한 자제하는 것이 좋다.
정리
- request 스코프는 http 요청마다 빈을 관리한다.
- http 요청 → http 응답까지 생명 주기이다.
- http 요청이 오기까지 빈이 없으므로 지연이 필요하다.
- Provider
- 프록시 객체
- 요청별 로그를 구분할 경우 사용하면 유용하다.