Spring/[inflearn]스프링 핵심 원리 - 기본편

빈 스코프 - 웹 스코프 사용 시 문제해결

hongdangmoo 2023. 9. 19. 14:35

-> 이전에 request 스코프 코드를 만들고 실행했을 때 HTTP 요청이 들어오지 않아서 request 스코프 빈이 생성되지 않아 에러가 발생했다. Provider, 프록시를 사용하여 에러를 해결한다.

 

◎ Provider 사용

 

LogDemoController

 

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 practice.core.common.MyLogger;

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) { // HttpServletRequest를 통해 요청 URL(http://localhost:8080/log-demo)을 받는다.
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL); // requestURL 값을 myLogger에 저장

        myLogger.log("controller test"); // 로그를 남긴다.
        logDemoService.logic("testId");
        return "OK";
    }
}

 

LogDemoService

 

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
import practice.core.common.MyLogger;

@Service
@RequiredArgsConstructor
public class LogDemoService { // 서비스 계층
    private final ObjectProvider<MyLogger> myLoggerProvider;

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

-> 결과

- 애플리케이션을 실행시키고 localhost:8080/log-demo에 접속하면 정상적으로 작동한다.

- 또한 로그도 정상적으로 출력되었다.

- ObjectProvider를 통해 ObjectProvider.getObject()를 호출하는 시점까지 request 스코프 빈의 생성을 지연할 수 있다.

- ObjectProvider.getObject()를 호출하는 시점에는 HTTP 요청이 들어와서 진행 중이기 때문에 request 스코프 빈이 정상적으로 생성된다.

- ObjectProvider.getObject()를 LogDemoController, LogDemoService에서 각각 한 번씩 따로 호출하여도 같은 HTTP 요청이면 동일한 스프링 빈이 반환된다.

 

◎ 프록시 사용

-> MyLogger 클래스에 proxyMode = ScopedProxyMode.TARGET_CLASS를 추가한다. 

-> 적용대상이 클래스면 TARGET_CLASS를 선택하고, 인터페이스라면 INTERFACE를 선택한다.

-> proxyMode = ScopedProxyMode.TARGET_CLASS로 클래스를 지정하면 MyLogger의 가짜 프록시 객체를 만들고 HTTP request와 관계 없이 가짜 프록시 객체를 다른 빈에 미리 주입할 수 있다.

-> LogDemoController와 LogDemoService 클래스를 Provider 적용 이전으로 수정한다.

 

LogDemoController

 

@Controller
@RequiredArgsConstructor
public class LogDemoController { // 로거가 작동하는지 확인하는 테스트용 컨트롤러
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) { // HttpServletRequest를 통해 요청 URL(http://localhost:8080/log-demo)을 받는다.
        String requestURL = request.getRequestURL().toString();

        System.out.println("myLogger = " + myLogger.getClass()); // 주입된 MyLogger 확인
        myLogger.setRequestURL(requestURL); // requestURL 값을 myLogger에 저장

        myLogger.log("controller test"); // 로그를 남긴다.
        logDemoService.logic("testId");
        return "OK";
    }
}

 

LogDemoService

@Service
@RequiredArgsConstructor
public class LogDemoService { // 서비스 계층
    private final MyLogger myLogger;

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

 

-> 결과

- 실행해보면 MyLogger는 CGLIB라는 라이브러리로 클래스를 상속받은  가짜 프록시 객체를 만들어서 주입된다.

- Scope의 'proxyMode = ScopedProxyMode.TARGET_CLASS'를 대상 클래스에 지정하면 스프링 컨테이너는 CGLIB라는 바이트 코드를 조작하는 라이브러리를 사용하여 MyLogger클래스를 상속받은 가짜 프록시 객체를 생성한다.

- 따라서 애플리케이션을 실행했을 때 myLogger를 확인해보면 순수 MyLogger 클래스가 아니라 CGLIB가 붙어서 등록된 객체가 가짜 프록시 객체라는 것을 확인할 수 있다.

- MyLogger의 기능을 호출하는 시점에 진짜 MyLogger를 찾아 동작한다.

 

MyLogger 프록시 객체 사용 흐름

- 가짜 프록시 객체는 해당 객체에 있는 기능에 대한 요청이 들어오면 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.

- 가짜 프록시 객체는 진짜 객체를 찾는 방법을 알고 있다.

- 클라이언트가 myLogger.log()를 호출하면 가짜 프록시 객체의 메서드를 호출한다.

- 프록시 객체는 request 스코프의 진짜 myLogger.log()를 호출한다.

- 프록시 객체는 진짜 객체를 상속받아 만들어진 객체기 때문에 해당 객체를 사용하는 클라이언트 입장에서는 프록시 객체인지 진짜 객체인지 모르는 상태로 마치 프록시 객체가 진짜 객체인 것 처럼 사용한다.

- 프록시 객체를 사용하여 마치 싱글톤 빈을 사용하는 것처럼 request 스코프 빈을 사용한다.

- 싱글톤 빈을 사용하는 것처럼 request 스코프 빈을 사용하지만 실제 싱글톤 빈은 아니므로 주의해서 사용해야 한다. 

- request 스코프와 같은 특수한 스코프는 필요한 곳에 최소화하여 사용해야 한다. 무분별하게 사용 시 코드의 유지보수가 어려워진다.

 

※ Provider와 프록시 객체 사용의 핵심

- Provider나 프록시 객체를 사용하는 방식의 핵심은 진짜 객체 조회를 필요한 시점까지 지연처리하는 것이다.

 

 

☆ 참고

[인프런]스프링 핵심 원리 - 기본편