티스토리 뷰
Web Scope
웹 스코프는 웹 환경에서만 동작하는 특징을 가지고 있으며 프로토타입 스코프와 다르게
종료 시점까지 스프링 컨테이너가 관리하기 때문에 종료 메서드를 호출해준다
웹 스코프를 사용하는 이유는 클라이언트에게 서비스를 제공하다 문제가 생겼을 때
보통 로그를 확인하여 디버깅을 진행한다
하지만 이때 동시에 접속한 여러 고객들의 요청이 뒤죽박죽 섞여 있으면 아주 힘든 상황이 될 것이다
그래서 공통적인 포맷으로 깔끔하게 로그들이 정리되어서 출력되도록 해주는 것이 웹 스코프의 역할이다
웹 스코프의 종류는 아래와 같이 다양하다
1. request
HTTP 요청에 하나가 들어오고 나갈 때까지 유지되는 스코프이다
각각의 HTTP요청 마다 별도의 빈 인스턴스가 생성되고 관리된다
2. session
HTTP Sessionr과 동일한 생명주기를 가지는 스코프
3. application
서블릿 컨텍스트와 동일한 생명주기를 가지는 스코프
4. websocket
웹 소켓과 동일한 생명주기를 가지는 스코프
스코프들이 가지는 생명주기 범위만 다르지 이 스코프들이 작동하는 방식은 다 비슷하다고 한다
Request Scope의 예제로 이 웹 스코프가 어떻게 작동하는지 알아보자
Request Scope
동시에 여러 HTTP 요청이 오게 되면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다 (뒤죽박죽)
이럴 때 Reqeust Scope를 사용하면 로그들을 깔끔하게 만들 수 있다
공통적으로 만들 포맷 : [UUID] [requestURL] {message}
UUID로 HTTP 요청을 구분하고 requestURL로 어떤 URL을 요청하였는지 로그를 만들어보자
@Component
@Scope(value = "request") //request 스코프로 지정 (HTTP 요청마다 새로운 빈 생성 및 종료)
public class MyLogger {
private String uuid;
private String requestURL;
// requestURL을 setter로 만든 이유는 빈이 생성되는 시점에서는 URL을 알수 없기 때문에
// Controller단에서 요청할때 URL값을 setter로 넣는다
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public void log(String message) {
System.out.println("[" + uuid + "]" + "[" + requestURL + "]" + "{" + message + "}");
}
@PostConstruct
public void init() {
// @PostConstruct를 사용하여 빈이 초기화 될때 uuid를 생성하여 필드에 저장
uuid = UUID.randomUUID().toString();
System.out.println("[" + uuid + "] request scope bean create" + this);
}
@PreDestroy
public void close() {
//@PreDestroy를 사용하여 빈이 소멸될때 문구를 출력한다
System.out.println("[" + uuid + "] request cope bean close" + this);
}
}
우선 scope를 request scope로 지정하였다 이렇게 지정하게 되면 이 빈(MyLogger)은
HTTP 요청당 하나씩 생성되고 HTTP 요청이 끝나는 시점에 소멸된다
또한 이 빈이 생성되는 시점에 @PostConstruct초기화 메서드를 사용해 uuid를 생성하고 저장한다
마지막으로 빈이 소멸되는 시점에 @PreDestroy를 사용해서 종료 메시지를 남긴다
정리하자면 받은 HTTP Request를 MyLogger에 저장하는 것이다 이때 핵심은 Requset Scope로 지정하였기 때문에
각각 요청이 새로운 빈으로 등록되어 값이 뒤죽박죽 섞이지 않는 걸 보장한다
컨트롤러를 만들어 실제로 실행을 시켜보자
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final ObjectProvider<MyLogger> myLoggerProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
//provider를 사용하여 새로 생기는 객체에 대한 의존성을 찾아 주입할 수 있도록 하였다
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
log-demo라는 URL로 접속을 하면 아래와 같이 각기 다른 로그들이 남은걸 확인할 수 있었다
[dbf70530-8829-44b1-aaa9-606a8612900f] request scope bean createhello.core.common.MyLogger@6514fb53
[dbf70530-8829-44b1-aaa9-606a8612900f][http://localhost:8080/log-demo]{controller test}
[dbf70530-8829-44b1-aaa9-606a8612900f][http://localhost:8080/log-demo]{service id =testId}
[dbf70530-8829-44b1-aaa9-606a8612900f] request scope bean closehello.core.common.MyLogger@6514fb53
[04c72a9b-bb23-4add-8200-f59a947ff897] request scope bean createhello.core.common.MyLogger@38bbc63a
[04c72a9b-bb23-4add-8200-f59a947ff897][http://localhost:8080/log-demo]{controller test}
[04c72a9b-bb23-4add-8200-f59a947ff897][http://localhost:8080/log-demo]{service id =testId}
[04c72a9b-bb23-4add-8200-f59a947ff897] request scope bean closehello.core.common.MyLogger@38bbc63a
[00a44ffc-f0a2-45f1-98b5-7467b6db95f4] request scope bean createhello.core.common.MyLogger@2e4b890f
[00a44ffc-f0a2-45f1-98b5-7467b6db95f4][http://localhost:8080/log-demo]{controller test}
[00a44ffc-f0a2-45f1-98b5-7467b6db95f4][http://localhost:8080/log-demo]{service id =testId}
[00a44ffc-f0a2-45f1-98b5-7467b6db95f4] request scope bean closehello.core.common.MyLogger@2e4b890f
즉 클라이언트가 동시에 요청을 한다고 해도 아래와 같이 각기 다른 빈으로 다른 로그를 남기게 하는 것이다
위의 예시에서는 provider를 사용하였는데 이보다 더 나은 방법인 proxy를 알아보자
Proxy
아래와 같이 프록시 모드를 설정하면 프록시를 만들어 동작하게 되며
기존에 provider를 사용했을 때처럼 정상적으로 동작한다
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
이렇게 프록시로 선언한 객체를 출력해보면 아래와 같이 나온다
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$2e9b720f
CGLIB 즉 스프링 컨테이너가 나의 클래스를 상속 받아 가짜 프록시 객체를 만든 것인데
이렇게 가짜 객체를 스프링 컨테이너에 등록하여 의존관계를 주입할 때도 이 가짜 프록시 객체를 주입하게 된다
가짜 프록시는 그 객체의 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직을 가지고 있다는 것이다
클라이언트가 요청하는 myLogger는 Proxy 즉 가짜를 호출한 것이고, 이를 다형성을 통해 진짜 같이 사용하는 것이다
그리고 실제 요청이 들어왔을 때 실제 빈을 호출한다
또한 이렇게 생성된 Proxy 객체는 reqeust scope와도 연관이 없다 그저 싱글톤처럼 동작하는 가짜 객체일 뿐이다
프록시(provider도 마찬가지)의 핵심은 실제 요청이 올 때까지 진짜 객체의 조회를 지연시킨다는 것이다
(프록시는 웹 스코프뿐만 아니라 AOP같은 경우에도 활용하여 사용할 수 있다)
핵심정리
웹 스코프를 사용하여 클라이언트가 요청한 로그들을 뒤죽박죽 섞이지 않게 정리할 수 있으며
이를 통해 문제가 발생하였을 때 더욱 용이하게 디버깅을 할 수가 있다
웹 스코프의 동작 방식은 비슷해서 request scope로 예시를 만들었을 때
HTTP의 요청부터 종료까지 새로운 객체를 생성하여 관리하기 때문에
동시에 요청이 들어온다 하더라도 각기 다른 객체로 인식하여 로그를 남길 수 있다
하지만 이러한 웹 스코프는 꼭 필요한 시점에서만 사용하는 것이 좋다고 한다
너무 남발하게 되면 무수히 많은 로그산에 깔려 유지보수 용이성을 떨어트린다
'Spring' 카테고리의 다른 글
[Spring-Boot] Spring과 Spring Boot의 차이 (0) | 2021.08.19 |
---|---|
[Spring] Spring Framework Module (스프링 프레임워크 모듈) (2) | 2021.08.19 |
[Spring] Bean & Bean Scope (0) | 2021.08.16 |
[Spring] Component Scan & Autowired (0) | 2021.08.13 |
[Spring] Singleton(싱글톤) (0) | 2021.08.13 |
- Total
- Today
- Yesterday
- Cache
- 세션
- 제이쿼리 인접 관계 선택자
- application/x-www-form-urlencoded
- OOP
- @ResponseStatus
- http
- 제이쿼리란
- 제이쿼리 탐색선택자
- 제이쿼리 직접 선택자
- 제이쿼리 기본 선택자
- spring
- DTO와 VO의 차이
- 제이쿼리 위치탐색선택자
- jQuery 직접 선택자
- uri
- @ExceptionHandlere
- Spring TypeConverter
- Spring API Error
- Spring Container
- 쿠키
- 캐시
- ResponseStatusExeceptionResolver
- maenco
- cookie
- Spring MVC
- 맨코
- ExceptionHandlerExceptionResolver
- Session
- DefaultHandlerExceptionResolver
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |