웹 스코프 | request scope
웹 환경에서만 동작하는.. 웹 스코프.. 공부시쟉 ✨
● 웹 스코프 종류
request
: HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프
: 각각의 HTTP 요청마다 별도의 빈 인스턴스 생성&관리
session : HTTP Session과 동일한 생명주기 가지는 스코프
application : 서블릿 컨텍스트와 동일한 생명주기 가지는 스코프
websocket : 웹소켓과 동일한 생명주기 가지는 스코프
↑ 나중에 공부하자
● web 라이브러리 추가
웹 스코프인 request 스코프를 공부하기 위해서 웹 라이브러리를 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-web'
그러면 CoreApplication (main메서드) 실행시 이전과 달라진 점
: 톰캣 서버가 뜬다. localhost:8080 접속 가능!
: 스프링부트는 내장 톰캣서버를 활용하여 웹서버와 스프링을 함께 실행시킨다.
↓ 톰캣서버가 떴다는 로그를 확인 가능하다.
main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
↓ localhost:8080에 접속 가능하다.
※ 스프링 부트는
웹 라이브러리가 없으면
- AnnotationConfigApplicationContext 기반으로 애플리케이션을 구동함
웹 라이브러리가 추가되면
- AnnotationConfigServletWebServerApplicationContext 기반으로 애플리케이션을 구동함
- 웹과 관련된 추가 설정 & 환경이 필요하기 때문이다.
request 스코프 예제
● MyLogger 구현 --- HTTP 요청별 로그 구분하기
동시에 여러 쓰레드에서 HTTP 요청이 올때 로그를 구분할 필요가 있다. 이럴때 request 스코프를 사용한다.
request 스코프를 활용하여 사용자의 로그를 남기는 기능을 구현해보자.
∨ 로그를 출력하기 위한 MyLogger 클래스 구현
∨ 요구사항 :
- 포맷 : [UUID][requestURL]{message}
- UUID를 통해 HTTP 요청이 구분된다
- 어떤 URL을 요청해서 남은 로그인지 확인 : requestURL 정보
cf. 로그는 어디서나 확인할 수 있는 공통사항이기에 common 패키지에 넣었다
package hello.core.common;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;
@Component
@Scope(value = "request")
public class MyLogger {
private String uuid;
private String requestURL;
// requestURL은 외부에서 setter로 받도록 한다.
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 = 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);
}
}
∨ MyLogger는 필드로 uuid와 requestURL을 가지고 있다.
- uuid는 생성시점에 부여되고
- requestURL은 어디선가 setter를 통해 넣어줄 수 있다.
=> 사용자로부터 받은 URL 요청을 처리하는 (프레젠테이션 층의) Controller에서 MyLogger의 setter를 사용해 URL을 넣어주면 되겠구나~~! (>v <*) /
=> 라고 생각했지만~~~~ ㅠㅅ ㅠ 실제론 그러지 말라고 바로 정정해주신다... ↓↓
넵!! 알겠습니다!! e(>v <*)/
● 컨트롤러와 서비스
자 이제 웹을 구성하기 위해
MyLogger를 멤버변수로 가지는 Controller와 Service를 구현해보자.
recall) 웹 애플리케이션의 구조 : https://cherryjubilee.tistory.com/5
∨ Controller
@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.getRequestURL().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
- Service와 Logger를 멤버변수로 가진다.
- @RequestMapping("log-demo") & HttpServletRequest를 통해서 요청 URL을 받는다.
=> Logger에 http://localhost:8080/log-demo를 setRequestURL하게 된다.
- 컨트롤러라는 것을 표시하기 위해 Logger에 "controller test"라는 log를 남기도록 했다.
- 어떤 서비스 로직에 testID를 String으로 건네주었다.
∨ Service
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
- 그냥 어떤 서비스로직이다.
- 뭐.. 그냥.. log를 찍게 했다..!
● request scope 빈 사용시 주의할 점
Controller는 스프링 컨테이너가 뜰 때 생성된다.
위 Controller는 MyLogger를 멤버로 가지고 있다.
문제는 멤버인 MyLogger가 사용자의 HTTP 요청이 뜰 때 생성되는 request scope라는 것이다.
(request scope의 생명주기 : 고객요청 들어오고 나가기까지)
Controller 생성시점에 MyLogger는 존재하지 않기 때문에 위 예제처럼만 구현하면 문제가 된다!
Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
해결방법 :
1. 이전에 배운 Provider를 적용한다.
2. 프록시 방식을 사용한다.
△ 바로 다음 학습으로 두가지 방식을 실습해보자 >ㅅ <