개발자 미니민의 개발스터디

[Spring 핵심원리 기본편] 웹 스코프

by mini_min

Spring 핵심원리 기본편 강의 마지막! 끝 !! 😇


웹 스코프 

웹 스코프는 웹 환경에서만 사용이 가능하다. 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리한다. 따라서 종료 메서드가 호출된다. (프로토타입은 스프링이 관리는 안해서 종료 메서드 destroy 는 실행하지 않았음) 

 

웹 스코프 종류

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

 

웹 환경 추가

웹 스코프는 웹 환경에서만 동작하므로 web 환경이 동작하도록 라이브러리를 추가한다.

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

 

request 스코프 예제 개발

동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다.
이럴 때 사용하기 좋은 것이 request 스코프이다.

기대하는 공통 포멧: [UUID][requestURL]{메시지}
UUID, requestURL 을 사용해서 HTTP 요청을 구분하자. 

 

1. Provider 사용하기

MyLogger 클래스

package hello.core.common;

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

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

@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("[" + uuid + "]" + "[" +requestURL + "] " + "request scope bean created:" + this);
    }

    @PreDestroy
    public void close(){
        System.out.println("[" + uuid + "]" + "[" +requestURL + "] " + "request scope bean close:" + this);
    }

}

 

Controller

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 ObjectProvider<MyLogger> myLoggerProvider; // 의존관계 주입을 받음

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL); //요청 URL 받아서 로그에 넣고 찍기

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

        return "OK";
    }


}

 

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 ObjectProvider<MyLogger> myLoggerProvider;

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

}
출력결과 

[dd769dee-cd5e-4fb2-8cd9-b001418c6dd1][null] request scope bean created:hello.core.common.MyLogger@42d95e06 [dd769dee-cd5e-4fb2-8cd9-b001418c6dd1][http://localhost:8080/log-demo] controller test [dd769dee-cd5e-4fb2-8cd9-b001418c6dd1][http://localhost:8080/log-demo] service id = testId [dd769dee-cd5e-4fb2-8cd9-b001418c6dd1][http://localhost:8080/log-demo] request scope bean close:hello.core.common.MyLogger@42d95e06

dd769dee-cd5e-4fb2-8cd9-b001418c6dd1 : UUID 는 모두 같다. 같은 요청임을 알 수 있다. 

 

2. 프록시 모드 사용하기

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

MyLogger 에 proxyMode = ScopedProxyMode.TARGET_CLASS 애노테이션 설정을 추가한다. 

위에 코드를 추가하면 CGLIB 라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 생성한다. 
결과를 확인해보면 순수한 MyLogger 클래스가 아니라 MyLogger$$EnhancerBySpringCGLIB 라는 클래스로 만들어진 객체가 대신 등록된 것을 확인할 수 있다. 
그리고 스프링 컨테이너에 myLogger 라는 이름으로 진짜 대신 가짜 프록시 객체를 등록한다. 
그래서 의존관계 주입도 가짜 프록시 객체가 주입된다.

 

가짜 프록시 객체는 요청이 오면 그 때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다. 
가짜 프록시 빈은 내부에 실제 MyLogger 를 찾는 방법을 알고 있다. 
가짜 프록시 객체는 request 스코프의 진짜 myLogger.logic() 을 호출한다. 
가짜 프록시 객체는 원본 클래스를 상속받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 원본인지 아닌지도 모르게 동일하게 사용할 수 있다. 

 

 ✨가짜 프록시 객체는 실제 request scope 와는 관계가 없다. 

 

특징 정리

프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope 를 사용할 수 있다. 
단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 다형성과 DI 컨테이너가 가진 큰 장점이다. 
클라이언트 코드 변경 필요 없! 다! 
꼭 웹 스코프가 아니어도 프록시는 사용할 수 있다. 

 

주의점
마치 싱글톤 처럼 사용되는 것에 주의하자!
특별한 scope 는 꼭 필요한 때에 사용하자. 무분별하게 사용하지 않도록 주의하자. 

 

 

 

 

블로그의 정보

개발자 미니민의 개발로그

mini_min

활동하기