[스프링 기초] request 스코프

LTEN·2022년 8월 2일
0

스프링 기초

목록 보기
5/6

※본 글은 김영한님의 '자바 스프링 완전정복 시리즈' 강의를 바탕으로 작성한 글입니다.

이번엔 스프링 컨테이너가 제공하는 스코프 중 하나인 웹 스코프 중 request 스코프에 대해 알아보겠습니다.

request 스코프로 관리되는 빈은 ① 스코프의 소멸까지 스프링이 관리하고, ② HTTP 요청마다 별도의 빈 인스턴스가 생성된다는 특징을 갖습니다.

코드를 통해서 그 예시를 살펴보겠습니다.

HTTP 연결 별로 로그를 기록하는 MyLogger 클래스를 살펴보겠습니다.

package hello.core.common;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
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);
    }
}

요청에 대한 정보(uuid, URL)을 저장하고, 해당 정보를 바탕으로 로그를 남기는 기능을 수행합니다.

스코프를 request로 등록하면서 proxyMode를 설정해둔 것을 확인할 수 있습니다. 이에 대한 설명은 뒤에서 추가로 하겠습니다.

이제 실제로 웹에서 사용될 Controller와 Service 코드를 살펴보겠습니다.

package hello.core.web;

import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;

    public void logic(String id) {
        myLogger.log("service id = " + id);
    }
}

MyLogger에 의존하며, log를 남기는 서비스를 제공합니다.

이때 LogDemoService는 싱글톤으로 관리되어 등록되는데, 생각해보면 request 스코프의 객체는 HTTP 요청이 들어와야 생성됩니다.
그렇다면 LogDemoService가 생성되는 시점에 MyLogger가 생성되어 있을까요?
그렇지 않기 때문에, 다음과 같은 코드를 추가해준 것입니다.

proxyMode = ScopedProxyMode.TARGET_CLASS

이렇게 등록할 경우, 스프링에서 MyLogger를 상속 받은 각자 프록시 객체를 생성하고, 실제로 LogDemoService에서 주입받는 빈도 그 객체입니다. (MyLogger$EnhancerBySpring과 같은 이름일 것입니다.)
해당 프록시 객체에서는 마치 Provider 처럼 request가 발생하면 실제 HTTP 요청에 따라 생성된 MyLogger를 조회해줍니다.

실제로 프로토타입을 다룰 때 사용하는 것과 동일한 방법으로 Provider 자체를 이용할 수도 있습니다.

핵심은 진짜 객체 조회가 필요한 시점까지 미룰 수 있다는 점입니다.

package hello.core.web;

import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@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);
        
        
        try {
            Thread.sleep(1000L);
        }
        catch(Exception e){
        }
        logDemoService.logic("testId");

        myLogger.log("controller test");
        logDemoService.logic("testId");

        return "OK";
    }
}

log-demo로 request가 들어오면 로그를 남기도록 합니다.

여기서 logDemoController에 주입된 MyLogger와 logDemoService에 주입된 MyLogger는 HTTP request 별로 같은 객체입니다.

이것이 가능한 이유, 즉 request 스코프의 빈이 HTTP request 별로 하나씩 할당될 수 있는 이유는, HTTP request 별로 쓰레드를 하나씩 할당하여 처리하기 때문입니다.

log-demo로 request를 빠르게 2번 발생시킨 후 남은 로그를 보겠습니다.


request 별로 uuid를 구분하여 잘 로그를 남기는 것을 볼 수 있습니다.

profile
백엔드

0개의 댓글