웹 스코프
- 웹 환경에서만 동작
- 웹스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출된다.
종류
- request: HTTP 요청 하나가 들어오고 나갈때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
- session: HTTP Session과 동일한 생명주기를 가지는 스코프
- application: 서블릿 컨텍스느와 동일한 생명주기를 가지는 스코프
- websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

Request 스코프 예제만들기
build.gradle에 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
위 라이브러리를 추가하면 톰캣 포트가 추가되며 웹 어플리케이션이 작동한다.
웹 라이브러리를 추가하면 AnnotationConfigApplicationContext를 기반으로 애플리케이션을 구동하지않고
AnnotationConfigServletWebServerAplicationContext를 기반으로 애플리케이션을 구동한다.
여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵기 때문에 Request스코프를 사용해서 구분
- 기대하는 공통포맷: [UUID][RequestUrl]{Message}
- UUID를 사용해서 HTTP 요청을 구분
- reqeustUrl 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인
@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() {
String 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);
}
}
url은 생성되는 시점에서 알 수 없으므로 외부에서 setter로 지정해준다.
@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.getRequestURI().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
Controller를 작성하고 실행시키게 되면 오류가 발생한다
MyLogger는 request 스코프로 지정된 빈인데 웹 관련 요청이 없어서 생성 자체가 안되기 떄문에 어플리케이션
실행되면서 오류가 발생한다. MyLogger를 빈으로 등록하려면 어플리케이션 실행할때는 생성하지 않다가 실질적으로 요청이 들어왔을때 빈을 조회 및 생성해야한다.
스코프와 Provider
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURI().toString();
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
// [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean create: hello.core.common.MyLogger@1f7962b6
// [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]controller test
// [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]service id: testId
// [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean close: hello.core.common.MyLogger@1f7962b6
myLogger를 ObjectProvider를 사용하여 request가 왔을때에만 DL하여 로그를 출력할 수 있다.
스코프와 프록시
@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.getRequestURI().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id: " + id);
}
}
위 예제와 달리 proxyMode = ScopedProxyMode.TARGET_CLASS를 @Scope 애노테이션 내에 지정해주면
Provider를 사용하지 않아도 잘 동작한다.
로그를 찍어보면
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$24131e0b와 같이 나오는데 여기의 MyLogger는 스프링이 조작해서 생성한 myLogger를 반환함을 알 수 있다.
이런 경우 MyLogger의 가짜 프록시 클래스를 만들어 두고 실제 기능호출을 할 경우 진짜 빈을 생성해서 가져온다.
Provider든 프록시든 핵심은 진짜 객체를 조회하기 전까지 지연처리하는것
단지 애노테이션 설정 변경만으로 원본객체를 프록시 객체로 대체할 수 있는 것이다.
대상이 Class 일 경우: ScopedProxyMode.TARGET_CLASS
대상이 Interface 일 경우 : ScopedProxyMode.INTERFACES
댓글