[김영한님 스프링 핵심원리] request scope

2022. 11. 5. 15:42Spring

728x90

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

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

}

init 메소드에서는 다른 uuid와 겹치지 않는 uuid를 만들어준다. 또한 MyLogger 객체의 참조값을 보여준다.

close 메소드에서는 MyLogger 객체의 참조값을 보여준다.

 

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final ObjectProvider<MyLogger> myLoggerProvider;
 
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        System.out.println("logDemo메소드");
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

 

위 코드에서 알아야 할 것은 LogDemoController가 싱글톤이고 MyLogger가 request scope이기때문에, ObjectProvider를 사용해야 한다는 것이다.

자세히 설명하자면, LogDemoController 빈이 생성되고, LogDemoService와 MyLogger에 의존관계가 주입된다.

하지만, MyLogger는 request scope이기 때문에, 요청이 온 후에 빈이 생성된다.

따라서, MyLogger 빈 생성 시기를 늦춰줘야한다.

 

그리고 이를 실행해서 로그를 보면 알 수 있는 사실이 더 있다.

uuid와 MyLogger 참조값을 유심히 보면 알 수 있다.

바로, request scope은 동시에 여러 요청이 온다 하더라도 요청마다 각각 객체를 따로 관리한다는 것이다.

 

 

위에서는 ObjectProvider<T>를 사용해서 MyLogger 빈 생성을 미뤘는데,

아래에서는 프록시를 이용하여 MyLogger 빈 생성을 뒤로 미룰 것이다.

 

@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);
    }

}

@Scope의 proxyMode = ScopedProxyMode.TARGET_CLASS를 설정하면 스프링 컨테이너는 CGLIB라는 바이트 코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.

 

CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다. 

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger; // request 스코프와 싱글톤 스코프의 생성시기가 다르다는 것을 인지해야함
    // 싱글톤 스코프가 먼저 생기고, request는 request가 와야 생김 따라서, ObjectProvider<T>를 사용해서 빈의 생성 시기를 미룸.

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        System.out.println("logDemo메소드");
        String requestURL = request.getRequestURL().toString();
        System.out.println("myLogger = " + myLogger.getClass());
        myLogger.setRequestURL(requestURL);
        System.out.println("myLogger = " + myLogger.getClass());
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

후에 /log-demo로 요청이 오면, 

가짜 MyLogger를 주입하고 MyLogger 기능이 실제로 동작하는 시점에서 가짜 프록시 객체는 내부에 진짜 MyLogger 빈을 스프링 컨테이너에 요청하는 위임 로직을 실행한다.

 

 

가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에, 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지 모르게, 동일하게 사용할 수 있다.(다형성)

 

가짜 프록시 객체는 실제 request scope과는 관계가 없고, 내부에 단순히 위임 로직만 있고, 싱글톤 처럼 동작한다.

 

두 방법의 핵심은 진짜 객체 조회를 꼭 필요한 시점까지 지연 처리한다는 점이다.

 

 

 

이상입니다~!!

728x90