minOS

빈 스코프(5) - request 스코프 예제 만들기 본문

TIL/김영한의 스프링 핵심 원리

빈 스코프(5) - request 스코프 예제 만들기

minOE 2024. 5. 27. 19:19
728x90

request 스코프 예제 만들기  

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

다음과 같이 로그가 남도록 request스코프를 활용해 추가 기능을 개발해보자.
[d06b992f...] request scope been create
[d06b992f...] [http://localhost:8080/log-demo] cotroller test
[d06b992f...] [http://localhost:8080/log-demo] service id = testId
[d06b992f...] request scope been closed​


- 기대하는 공통 포맷 : [UUID][requestURL](message)
- UUID를 이용해서 HTTP 요청 구분하기
-requestURL 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인


MyLogger

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

    }

}

- 로그를 출력하기 위한 클래스
- @Scope(value = "request")를 사용하여 request 스코프 지정했다. 이 빈은 HTTP 요청 당 하나씩 생성되고, HTTP요청이 끝나면 소멸된다.
- 이 빈이 생성되는 시점에 자동으로 @PostConstruct 초기화 메서드를 사용하여 uuid 를 생성하여 저장한다.
- 이 빈은 요청 당 하나씩 생성되므로 , uuid를 저장해두면 다른 HTTP 요청과 구분할 수 있다.
- 이 빈의 소멸 시점에서 @PreDestroy를 사용해서 종료 메세지를 남긴다.
-'RequestURL'은 이 빈이 생성되는 시점을 알 수 없으므로 , 외부에서 setter로 입력 받는다.

LogDemoController

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

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

- 로거가 잘 작동하는지 확인하는 테스트용 컨트롤러다.
- 여기서 HttpServletRequest를 통해 요청 URL을 받았다.
  requestURL값

http://localhost:8080/log-demo


- 이렇게 받은 requestURL값을 myLogger에 저장한다. myLogger는 HTTP 요청 당 구분된다.
- 컨트롤러에서 controller test 라는 로그를 남긴다.

LogDemoService

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

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

    }

}

- 비지니스 로직이 있는 서비스 계층에서도 로그를 출력한다.
- 여기서 중요한 것은 request scope를 사용하지 않고 모든 정보를 서비스 계층에게 파라미터로 넘기면 지저분해진다.
- requestURL같은 web 과 관련된 정보는 서비스 계층까지 넘어가지 말아야한다. 웹과 관련된 부분은 컨트롤러까지만 사용해야한다. 서비스 계층은 웹 기술에 종속되지 않고 , 가급적 순수하게 유지하는 것이 유지 보수 관점에서 좋다.
- request scope의 MyLogger 덕분에 이런 부분을 파라미터로 넘기지 않고 , MyLogger 멤버변수에 저장해서 코드와 계층을 깔끔하게 유지할 수 있다.



실행 시 기대하는 출력

[d06b992f...] request scope been create
[d06b992f...] [http://localhost:8080/log-demo] cotroller test
[d06b992f...] [http://localhost:8080/log-demo] service id = testId
[d06b992f...] request scope been closed​



실행 시점에 오류 발생

 Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton

스프링 애플리케이션을 실행하는 시점에 싱글톤 빈은 생성해서 주입이 가능하지만 , request scope가 아직 생성되지 않았다. 이 빈은 고객 요청이 와야 생성할 수 있다. 

 

 

Provider 사용

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

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

 

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final ObjectProvider<MyLogger> myLoggerProvider;

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

    }

}

 
로그

 

- ObjectProvider 덕분에 ObjectProvider.getObject();를 호출하는 시점까지 request scope를 스프링 컨테이너에 요청하는 것을 지연시킬 수 있다.
- Spring 컨텍스트에서 요청 스코프 빈을 사용할 때 ObjectProvider를 사용하면, getObject() 메서드를 호출하기 전까지는 실제로 빈이 생성되지 않는다. 이렇게 하면 빈의 생성과 초기화를 필요한 시점까지 지연시킬 수 있다.
- ObjectProvider.getObject()을 호출하는 시점에는 HTTP 요청이 진행 중이므로 request scope가 정상처리 된다.

 

 

728x90