Published 2022. 1. 14. 12:25
반응형

스프링에는 웹 스코프, 싱글톤 스코프, 프로토타입 스코프 크게 3가지가 있다.
그 중 웹 스코프의 request 스코프를 알아 보자.

Request 스코프란


웹 스코프는 3가지로 나뉜다.

  • request : 웹 요청이 들어오고 나갈 때 까지
  • session : 웹 세션이 생성되고 종료될 때 까지
  • applicaiont : 웹의 서블릿 컨텍스트와 같은 범위 ???????

reqest 스코프는 http의 요청이 오고 응답해서 나갈 때 까지 스프링 빈에 관리되는 스코프이다.
싱글톤과는 다르게 http 요청 마다 개별로 빈을 생성해준다.

언제 사용할 수 있을까?


여러가지가 있겠지만 사용자별 로그에 사용될 수 있다.
10명의 사용자가 요청을 했는데 로그가 어떤 사용자인지 구분없이 보인다면 로그를 보기가 힘들 것이다.

http 요청 마다 새로운 빈을 생성하는 reqeust 스코프를 이용해 uuid 별로 요청을 구분하도록 사용해 볼 수 있다.

Provider를 이용하기

로그 클래스

스코프를 request로 설정하고, uuid를 초기화 시점에 주입한다.
uuid는 요청을 구분하기 위해 사용된다.

@Scope(value = "request")
@Component
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 close : " + this);
    }
}

컨트롤러 클래스

  • request 스코프는 http 요청이 들어와야 초기화가 된다. 그렇기 때문에
    스프링 컨테이너가 뜰 때 의존성 주입을 받을 수가 없다. 요청 자체가 없기 때문에 빈이 없기 때문이다.
    이럴 때 활용할 수 있는 것이 Provider이다. Provider를 이용해 빈 생성은 지연시켜 .getObject()시점
    초기화를 하는 방법이다.
  • 정말 요청별로 빈이 생성되어, 응답이 끝날 때 까지 남아있는지 확인하기 위해 딜레이 1초를 주었다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final ObjectProvider<MyLogger> myLoggerProvider;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) throws InterruptedException {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestUrl(requestURL);
        Thread.sleep(1000);

        myLogger.log("controller test");

        return "OK";
    }
}

결과

로그를 보면 uuid가 c49a4877-e061-4919-b7d2-19cc76868be8, ee1af249-12f3-4aa5-9af2-b940c595b998 두 개가 있다.
이는 요청을 두 번 했기 때문에 각각 생긴 것이다. 객체도 @5385447b, @57dcc587 이렇게 요청별로 생성되었다.

[c49a4877-e061-4919-b7d2-19cc76868be8] request scope bean create : hello.core.common.MyLogger@5385447b
[ee1af249-12f3-4aa5-9af2-b940c595b998] request scope bean create : hello.core.common.MyLogger@57dcc587
[c49a4877-e061-4919-b7d2-19cc76868be8][http://localhost:8080/log-demo]controller test
[c49a4877-e061-4919-b7d2-19cc76868be8] request scope bean close : hello.core.common.MyLogger@5385447b
[ee1af249-12f3-4aa5-9af2-b940c595b998][http://localhost:8080/log-demo]controller test
[ee1af249-12f3-4aa5-9af2-b940c595b998] request scope bean close : hello.core.common.MyLogger@57dcc587

ScopedProxyMode.TARGET_CLASS 를 이용하기

Provider 보다 더 간단한 방법이 있다. ScopedProxyMode.TARGET_CLASS 옵션이다.

옵션을 주면 마치 싱글톤 처럼 사용을 할 수 있다.

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

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final MyLogger myLogger;
        ...
}

어떻게 이게 가능할까?

프록시에 의해서 가능하다. 프록시는 가짜 객체를 말 하는데 여기서 MyLogger는 CGLIB을 이용해
가짜 객체인 상태이다. 껍데기일 뿐이고, http요청이 올 때 까지 빈 생성을 지연 시키기 위해 가짜 객체라도 주입 해놓은 것이다.
실제 요청이 들어오면 그 때 주입을 하게 된다.

정리 하자면

  • CGLIB으로 실제 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.(지연 하는 것이다)
  • 프록시 객체는 실제 요청이 오면 그 때 내부에서 실제 빈을 달라고 컨테이너에 요청한다.
  • 프록시는 request 스코프가 아닌 그냥 가짜 객체이다.

로그를 확인해 보면 프록시 객체라는 것을 확인할 수 있다.

myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$92628da2

주의 사항

  • 싱글톤 같기 때문에 잘못 인식하고 사용할 가능성이 있다.
    이런 특별한 상황은 최대한 자제하는 것이 좋다.

정리


  • request 스코프는 http 요청마다 빈을 관리한다.
  • http 요청 → http 응답까지 생명 주기이다.
  • http 요청이 오기까지 빈이 없으므로 지연이 필요하다.
    • Provider
    • 프록시 객체
  • 요청별 로그를 구분할 경우 사용하면 유용하다.
반응형
복사했습니다!