1. Web Scope
- 웹 스코프는 웹 환경에서만 동작한다.
- 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리한다.
- 따라서 종료 메서드가 호출된다.
2. Web Scope 종류
- request : HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
- session : HTTP Session과 동일한 생명주기를 가지는 스코프
- application : 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프
- websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프
3. Request Scope 예제 만들기
(1) 웹 환경 추가
웹 스코프는 웹 환경에서만 동작하므로 web 환경이 동작하도록 라이브러리를 추가하자.
//build.gradle에 추가
//web 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
spring-boot-starter-web 라이브러리를 추가하면 스프링 부트는 내장 톰켓 서버를 활용해서 웹서버와 스프링을 함께 실행시킨다.
스프링 부트는 웹 라이브러리가 없으면 우리가 지금까지 학습한 AnnotationConfigApplicationContext을 기반으로 애플리케이션을 구동한다. 웹 라이브러리가 추가되면 웹과 관련된 추가 설정과 환경들이 필요하므로 AnnotationConfigServletWebServerApplicationContext를 기반으로 애플리케이션을 구동한다.
(2) 예제로 확인하기
MyLogger
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;
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);
}
}
- 로그를 출력하기 위한 MyLogger 클래스이다.
- @Scope(value = "request")를 사용해서 request 스코프를 지정했다. 이제 이 빈은 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.
- 생성되는 시점에 @PostConstruct 초기화 메서드를 사용해서 uuid를 생성해서 저장해둔다. 빈은 HTTP 요청 당 하나씩 생성되므로, uuid를 저장해두면 다른 HTTP 요청과 구분할 수 있다.
- 빈이 소멸되는 시점에 @PreDestroy를 사용해서 종료 메시지를 남긴다.
- requestURL은 이 빈이 생성되는 시점에 알 수 없으므로, 외부에서 setter로 입력받는다.
LogDemoService
package hello.core.logdemo;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic (String id) {
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id = " + id);
}
}
- 비즈니스 로직이 있는 서비스 계층에서도 로그를 출력해보자.
- request Scope를 사용하지 않고 파라미터로 이 모든 정보를 서비스 계층에 넘긴다면, requestURL 같은 웹과 관련된 정보가 웹과 관련없는 서비스 계층까지 넘어가게 된다. 웹과 관련된 부분은 컨트롤러까지만 사용해야 한다.
- 서비스 계층은 웹 기술에 종속되지 않고, 가급적 순수하게 유지되는 것이 유지보수 관점에서 좋다.
- request scope의 MyLogger 덕분에 이런 부분을 파라미터로 넘기지 않고, MyLogger의 멤버 변수에 저장해서 코드와 계층을 깔끔하게 유지할 수 있다.
LogDemoController
package hello.core.web;
import hello.core.common.MyLogger;
import hello.core.logdemo.LogDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequiredArgsConstructor
public class LogdemoController {
private final LogDemoService logDemoService;
private final ObjectProvider<MyLogger> myLoggerProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
- HttpServletRequest를 통해서 요청 URL을 받았다.
- requestURL 값 http://localhost:8080/log-demo
- requestURL 값을 myLogger에 저장해둔다. myLogger는 HTTP 요청 당 각각 구분된다.
- 컨트롤러에서 controller test라는 로그를 남긴다.
결과
- main() 메서드로 스프링 실행하고, 웹 브라우저에 localhost:8080/log-demo 입력
- ObjectProvider로 의해 ObjectProvider.getObject()를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다.
- ObjectProvider.getObject()를 호출하는 시점에는 HTTP 요청이 진행중이므로 request scope 빈이 생성이 정상 처리된다.
- ObjectProvider.getObject()를 LogDemoController, LogDemoService에서 각각 한 번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다.
본 글은 김영한 님의 "스프링 핵심 원리"(인프런) 유료 강의를 들으며 요약, 정리하고 일부 정보를 추가 작성한 것입니다.
반응형
'Spring(JAVA Framework) > Spring Core' 카테고리의 다른 글
Bean Scope - 1 (Prototype) (0) | 2021.08.20 |
---|---|
빈(Bean) 생명 주기 콜백 (0) | 2021.08.19 |
조회한 모든 빈이 가져오기 (List, Map) (0) | 2021.08.19 |
Lombok 라이브러리 (0) | 2021.08.19 |
@ Autowired 사용시 조회 빈이 중복되는 경우 (0) | 2021.08.19 |
댓글