웹 스코프의 특징
- 웹 스코프는 웹 환경에서만 동작합니다
- 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리하기에 종료 메서드가 호출됩니다
웹 스코프의 종류
- request : HTTP 요청 하나가 들어오고 나갈 떄 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리됩니다
- session : HTTP Session과 동일한 생명주기를 가지는 스코프
- application : 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프
- websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프
학습 내용에 따라 requset 스코프를 기준으로 다루어 보겠습니다. 나머지는 범위만 다르고 동작 방식은 비슷합니다
그림과 같이 클라이언트 A와 B가 동시에 싱글톤 Controller에 HTTP request요청을 하였지만
requset scope인 MyLogger 호출 시 각각 A, B 전용 객체가 생성됩니다.
* 싱글톤 빈과 함께 사용시 문제점
예시로 아래와 같이 로그를 출력하는 'LogDemoController'가 있다.
해당 LogDemoController에서 'LogDemoService'는 정상적으로 의존성 주입이 되지만
'MyLogger'라는 request스코프 객체는 의존성 주입이 되지 않아 실행시 오류를 발생시킨다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService; // 싱글톤 스코프
private final MyLogger myLogger; // request 스코프
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
myLogger.log("controller test");
logDemoService.logic("service test");
return "OK";
}
}
@Component
@Scope(value = "request")
public class MyLogger {
// 생략
}
오류
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;
* 발생이유
스프링 어플리케이션을 실행하는 시점에 싱글톤 빈들은 생성해서 주입해준다.
하지만 request 스코프 빈은 아직생성되지 않았기 때문에 스프링 컨테이너에서 찾을 수 없다.
request 스코프 빈은 실제 고객의 요청이 와야 생성 및 초기화 할 수 있다. (HTTP requset 요청)
즉, Scope가 request이기 떄문에 실제 생성 및 초기화 시점은 HTTP requst 시점이 된다.
해결방법으로는 두가지가 있다.
1. ObjectProvider를 사용한 Provider 방식
2. 프록시 방식(추천)
1. ObjectProvider을 이용한 방식
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService; // 싱글톤 스코프
// 1. Provider 사용
// 의존성 주입(DI) 시점에 MyLogger을 조회할 수 있는 provider을 제공 받는다.
private final ObjectProvider<MyLogger> myLoggerProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
// 1. Provider 사용
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("controller test");
logDemoService.logic("service test");
return "OK";
}
}
2. 프록시를 이용한 방식
해당 request scope 객체에 proxyMode 설정만 추가해주면 된다.
적용 대상이 인터페이스가 아닌 클래스면 TARGET_CLASS 를 선택하고
적용 대상이 인터페이스면 INTERFACES 를 선택한다.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
// 생략
}
이렇게 하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.
아래와 같이 컨트롤러 내부에서도 기존 싱글톤 빈을 사용하는 것 처럼 코드가 간결해진다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService; // 싱글톤 스코프
private final MyLogger myLogger; // request 스코프
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
myLogger.log("controller test");
logDemoService.logic("service test");
return "OK";
}
}
* 프록시 방식 정리
가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
- 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있다.
- 클라이언트가 myLogger.logic() 을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것이다.
- 가짜 프록시 객체는 request 스코프의 진짜 myLogger.logic() 를 호출한다.
- 가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다(다형성)
동작 정리
- CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
- 이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
- 가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있
고, 싱글톤 처럼 동작한다.
특징 정리
- 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있
다. - 사실 Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연
처리 한다는 점이다. - 단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이것이 바로 다형성과 DI 컨
테이너가 가진 큰 강점이다. - 꼭 웹 스코프가 아니어도 프록시는 사용할 수 있다.
주의점
- 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다.
- 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워
진다.
'Spring' 카테고리의 다른 글
[Spring] 빈 스코프(프로토타입 스코프 / prototype scope) (0) | 2021.11.29 |
---|---|
[Spring] ModelMapper 대신 Mapstruct 사용하기 (1) | 2021.10.17 |
[Spring] HiddenHttpMethodFilter로 GET,POST를 DELETE,PUT등 으로 받기_Spring Boot설정 (0) | 2021.08.26 |
[Spring] 유효성 검사(Validating Form) (0) | 2021.08.18 |
[Spring] HTTP 메시지 컨버터 (HttpMessageConverter) (0) | 2021.08.02 |