5 min read

<Spring> 스프링 핵심원리 이해 10 - 스프링 빈 스코프(웹 스코프)

웹 스코프

이전 포스팅에서 우리는 싱글톤과 프로토타입 스코프에 대해서 공부했다.다시 요약하자면 싱글톤은 스프링 컨테이너의 시작와 끝까지 모두 함께하는 스코프이고 프로토타입의 경우 컨테이너에서는 의존관계주입 그리고 초기화까지만 관리하는 스코프이다.

이번에는 웹 스코프에 대한 포스팅이다.

웹 스코프의 특징

  • 웹 스코프는 웹 환경에서만 동작한다.
  • 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리한다.따라서 종료 메서드가 호출된다.

웹 스코프의 종류

  1. request : HTTP Request 요청하나가 들어오고 나갈 때까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 인스턴스가 생성되고 관리된다.
  2. session: HTTP Session과 동일한 생명주기를 가지는 스코프
  3. application: ServletContext와 동일한 생명주기를 가지는 스코프
  4. websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
  • 해당 포스팅에선 request 스코프를 예제로 설명한다.나머지도 범위만 다르지 동작 방식은 비슷하기 때문이다.
HTTP request 요청 당 각각 할당되는 request 스코프
HTTP request 요청 당 각각 할당되는 request 스코프

Request Scope 예제를 위한 웹 환경 설정

  • build.gradle에 아래와 같이 web 패키지 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-web:2.6.2'

  • 해당 패키지는 스프링부트 내장 톰캣 웹서버를 띄워주는 역할을 수행한다.
  • 만약, build.gradle의 자바 버전과 현재 프로젝트의 자바 버전이 다르면 톰캣 서버 자체가 실행 되지 않는 오류가 발생한다.
  • 이러한 경우 Project Structure -> Project SDK를 build.gradle과 동일하게 설정해주자
  • Springboot 애플리케이션을 실행시키면 아래와 같이 8080 포트에 톰캣 서버를 실행시킴을 알 수 있다.
  • 이후 localhost:8080으로 접속하면 아래와 같이 WhiteLabel Error가 발생하는것이 정상동작하는 것이다.

Request Scope 예제 개발

  • 아래와 같이 Request 요청이 들어오면, 생성되고 삭제되는 하나의 cycle을 로그로 남겨주는 클래스를 Request Scope로 만들면 된다.
  • 먼저 로그 자체를 남겨주는 객체를 생성한다.
@Component
@Scope(value = "request")
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("MyLogger.init : " + this);
    }

    @PreDestroy
    public void close() {
        System.out.println("MyLogger.close : " + this);
    }
}
  • 이후 해당 객체를 주입 받아서 사용하는 Controller,Service를 생성한다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        //MyLogger myLogger = myLoggerObjectProvider.getObject();
        String string = request.getRequestURL().toString();
        myLogger.setRequestURL(string);

        myLogger.log("Controller Here");
        logDemoService.logic("testId");

        return "OK";
    }
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;
    
    public void logic(String id) {
        //MyLogger myLogger = myLoggerObjectProvider.getObject();
        myLogger.log("service id = " + id);
    }
}
  • 위와 같은 코드를 실행후, springboot 어플리케이션을 동작시키면 같은 ScopeNotActiveException 에러가 발생한다.
  • 알고나면 당연히 발생해야하는 에러가 발생했다. 우리는 현재 request 스코프를 적용시켰기 때문에 HTTP 리퀘스트가 들어올 경우 해당 Client 마다 서로 다른 빈들을 생성해서 response를 준다.즉,하나의 전제조건이 존재한다. HTTP 리퀘스트 요청이 들어와야하는 상황이다. 하지만 현재 문제점은 이러한 웹서버를 띄우기 위해서 HTTP request가 필요하다는 점이다.(영원히 들어오지 않는 요청을 기다리니 에러가 발생한다고 생각하면 된다)

Request Scope 문제 해결

  • 위와 같은 문제를 마주했을 경우 Proxy를 사용해서 간단하게 해결 가능하다.
@Component
@Scope(value = "request",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
    //...
}
  • @Scope의 어노테이션에 proxyMode에 ScopedProxyMode.TARGET_CLASS를 설정해주면 해결된다.
  • 해당 속성은 말 그대로 Proxy MyLogger 객체를 만들어서 모든 Client에게 우선적으로 가짜 객체를 주입해주는 역할을 한다.
  • 즉, request 요청이 들어오면 먼저 프록시 객체에게 들어가고,이후 해당 프록시 객체에서 진짜 스프링 빈을 찾아낸 뒤, 로직을 호출한다.