본문 바로가기
카테고리 없음

[Spring] 빈 스코프 - 웹 스코프, Request 스코프

by 쭈봉이 2021. 12. 23.

웹 스코프

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

종류

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

동시에 요청할 경우 각가 다른 Bean이 생성되고 관리된다.

Request 스코프 예제만들기

build.gradle에 추가

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

위 라이브러리를 추가하면 톰캣 포트가 추가되며 웹 어플리케이션이 작동한다.

웹 라이브러리를 추가하면 AnnotationConfigApplicationContext를 기반으로 애플리케이션을 구동하지않고

AnnotationConfigServletWebServerAplicationContext를 기반으로 애플리케이션을 구동한다.

 

여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵기 때문에 Request스코프를 사용해서 구분

  • 기대하는 공통포맷: [UUID][RequestUrl]{Message}
  • UUID를 사용해서 HTTP 요청을 구분
  • reqeustUrl 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인
@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() {
        String 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);
    }
}

url은 생성되는 시점에서 알 수 없으므로 외부에서 setter로 지정해준다.

@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.getRequestURI().toString();
        myLogger.setRequestURL(requestURL);

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

Controller를 작성하고 실행시키게 되면 오류가 발생한다

MyLogger는 request 스코프로 지정된 빈인데 웹 관련 요청이 없어서 생성 자체가 안되기 떄문에 어플리케이션

실행되면서 오류가 발생한다. MyLogger를 빈으로 등록하려면 어플리케이션 실행할때는 생성하지 않다가 실질적으로 요청이 들어왔을때 빈을 조회 및 생성해야한다.

 

스코프와 Provider

@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
    String requestURL = request.getRequestURI().toString();
    MyLogger myLogger = myLoggerProvider.getObject();
    myLogger.setRequestURL(requestURL);
    myLogger.log("controller test");
    logDemoService.logic("testId");
    return "OK";
}
//        [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean create: hello.core.common.MyLogger@1f7962b6
//        [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]controller test
//        [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]service id: testId
//        [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean close: hello.core.common.MyLogger@1f7962b6

myLogger를 ObjectProvider를 사용하여 request가 왔을때에만 DL하여 로그를 출력할 수 있다.

 

스코프와 프록시

@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
@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.getRequestURI().toString();

        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;

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

 

위 예제와 달리 proxyMode = ScopedProxyMode.TARGET_CLASS를 @Scope 애노테이션 내에 지정해주면 

Provider를 사용하지 않아도 잘 동작한다.

로그를 찍어보면

myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$24131e0b와 같이 나오는데 여기의 MyLogger는 스프링이 조작해서 생성한 myLogger를 반환함을 알 수 있다.

이런 경우 MyLogger의 가짜 프록시 클래스를 만들어 두고 실제 기능호출을 할 경우 진짜 빈을 생성해서 가져온다.

 

Provider든 프록시든 핵심은 진짜 객체를 조회하기 전까지 지연처리하는것

단지 애노테이션 설정 변경만으로 원본객체를 프록시 객체로 대체할 수 있는 것이다.

 

대상이 Class 일 경우: ScopedProxyMode.TARGET_CLASS

대상이 Interface 일 경우 : ScopedProxyMode.INTERFACES

댓글