웹 스코프

Gyuhan Park·2022년 9월 12일
0

spring

목록 보기
17/18

빈 스코프

: 빈이 존재할 수 있는 범위

싱글톤 스코프 : 스프링 컨테이너의 시작과 끝까지 함께하는 매우 긴 스코프
프로토타입 스코프 : 생성과 의존관계 주입, 그리고 초기화까지만 진행하는 스코프
웹 스코프 : 웹 환경에서만 동작

웹 스코프 종류

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
  • session: HTTP Session과 동일한 생명주기를 가지는 스코프
  • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

request 스코프

동시에 여러 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가 스프링 빈에 없다. 그래서 의존관계를 주입할 때 오류가 발생한다.

Provider

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

Proxy를 사용하면 ObjectProvider를 사용하지 않고 코드상 그냥 MyLogger를 갖다쓸 수 있다.

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}

적용 대상이 클래스 : ScopedProxyMode.TARGET_CLASS
적용 대상이 인터페이스 : ScopedProxyMode.INTERFACES
MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.

  • CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
  • 이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
  • 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.

핵심은 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.

[참고] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글