: 빈이 존재할 수 있는 범위
싱글톤 스코프
: 스프링 컨테이너의 시작과 끝까지 함께하는 매우 긴 스코프
프로토타입 스코프
: 생성과 의존관계 주입, 그리고 초기화까지만 진행하는 스코프
웹 스코프
: 웹 환경에서만 동작
request
: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.session
: HTTP Session과 동일한 생명주기를 가지는 스코프application
: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프websocket
: 웹 소켓과 동일한 생명주기를 가지는 스코프동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다.
이럴때 사용하기 딱 좋은것이 바로 request 스코프
다.
로그를 출력하기 위한 MyLogger 클래스를 request 스코프로 만들 것이다.
@Scope(value = "request")
를 사용해서 request 스코프로 지정했다. 이제 이 빈(MyLogger)은 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.
@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";
}
}
여기서 HttpServletRequest를 통해서 요청 URL을 받았다.
requestURL 값 : http://localhost:8080/log-demo
requestURL을 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가 발생했을 때부터 response 할 때까지인데 spring boot가 실행될 때는 request가 발생하기 전이니 MyLogger가 스프링 빈에 없다. 그래서 의존관계를 주입할 때 오류가 발생한다.
Dependency Lookup (DL)
: 외부에서 DI 받지 않고 직접 필요한 의존관계를 찾는 것
ObjectProvider
: 지정한 빈을 컨테이너에서 대신 찾아주는 DL 기능 제공
ObjectProvider.getObject()
를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.
아래 코드와 같이 ObjectProvider.getObject()
를 호출하는 시점까지 request scope 빈의 생성을 지연시켜 코드가 정상작동한다.
@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();
String requestURL = request.getRequestURL().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
Proxy를 사용하면 ObjectProvider를 사용하지 않고 코드상 그냥 MyLogger를 갖다쓸 수 있다.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
적용 대상이 클래스 : ScopedProxyMode.TARGET_CLASS
적용 대상이 인터페이스 : ScopedProxyMode.INTERFACES
MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.
CGLIB
라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.핵심은 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.