이전 편에서는 싱글톤과 프로토타입 스코프에 대해 정리했다.
이번 편에서는 실제 웹 애플리케이션에서 자주 사용되는 웹 스코프(Request, Session 등)의 개념과
스프링에서 이를 안전하게 사용하는 방법을 알아본다.
웹 스코프란?
웹 스코프는 웹 요청(Request)과 세션(Session)의 생명주기에 따라 빈의 생존 범위를 지정한다.
스코프 이름 | 생존 범위 |
request | 하나의 HTTP 요청이 들어와서 나갈 때까지 |
session | 하나의 HTTP 세션이 생성되어 종료될 때까지 |
application | 서블릿 컨텍스트(ServletContext) 범위 |
websocket | 웹소켓 연결이 유지되는 동안 |
Request 스코프 사용 예시
@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 closed: " + this);
}
}
- uuid를 통해 각 요청별로 로그를 구분할 수 있도록 한다
- 요청 URL을 저장하면 어떤 요청에서 남긴 로그인지 추적 가능하다
웹 스코프를 Controller에서 직접 사용할 때 발생하는 문제
@Controller
@RequiredArgsConstructor
public class LogDemoController {
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");
return "OK";
}
}
위 코드는 오류가 발생할 수 있다.
스프링 컨테이너가 생성될 때는 HTTP 요청이 존재하지 않기 때문에
@Scope("request") 빈을 주입하려 하면 IllegalStateException이 발생한다.
해결 방법 1: ObjectProvider 사용
@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";
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id: " + id);
}
}
- getObject()를 호출할 때 HTTP 요청이 존재하면 그 시점에 빈을 생성함
- Provider를 사용하면 HTTP 요청이 실제로 존재할 때까지 생성을 지연시킬 수 있다
해결 방법 2: 프록시(proxyMode) 사용
보다 깔끔한 해결책은 스프링이 제공하는 프록시 객체(proxy)를 활용하는 방법이다.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
...
}
@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
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id: " + id);
}
}
- @Scope(proxyMode = ...)로 설정하면, 가짜 프록시 객체가 주입
- 프록시는 HTTP 요청이 시작된 이후에 실제 MyLogger를 찾아서 동작을 위임함
- 코드도 간결하고 테스트도 쉬움
웹 스코프 사용 시 주의점
- 웹과 관련된 request, session 객체는 컨트롤러에서만 다루는 게 좋다
- 서비스나 레포지토리 계층에서는 웹 기술에 종속되지 않도록 설계하는 것이 유지보수에 유리하다
'Spring' 카테고리의 다른 글
[Spring 완전 정복 시리즈] 마지막편 - 기본편 완강 회고 및 마무리 (7) | 2025.08.02 |
---|---|
[Spring 완전 정복 시리즈] 23편 - 빈 스코프 완전 정복 (3): Provider vs 프록시, 그리고 정리 (1) | 2025.08.01 |
[Spring 완전 정복 시리즈] 21편 - 빈 스코프 완전 정복 (1): 싱글톤과 프로토타입 (0) | 2025.08.01 |
[Spring 완전 정복 시리즈] 20편 - 빈 생명주기 콜백과 초기화 전략 정리 (1) | 2025.07.31 |
[Spring 완전 정복 시리즈] 19편 - 자동 주입 고급 기능과 전략 패턴 적용법 (3) | 2025.07.30 |