본문 바로가기

Spring

스프링 핵심 원리 - 기본편 : 빈 스코프(2/2)

스프링 핵심 원리 - 기본편 : 빈 스코프(2/2)

- 웹 스코프

웹 스코프는 웹 환경에서 동작하는 스코프로 프로토타입 스코프와는 다르게 컨테이너가 생성과 소멸을 모두 관리한다. 때문에 소멸 메소드 또한 빈이 소멸하는 시점에 호출된다.

 

웹 스코프의 종류는 request, ssesion, application, websocket 가 있다. 참고 중인 강의에서는 request에 대한 예제로 진행된다. request 스코프는 HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프로, HTTP 요청마다 별도의 빈이 생성되고, 요청이 종료되면 소멸된다. 아래서 진행되는 예제는 각각 HTTP의 요청과 request 빈에 대한 로그를 출력하는MyLogger 클래스를 통해 진행된다.

 

  

- request 스코프 예제 만들기

시작하기에 앞서 spring-boot-starter-web 라이브러리를 추가했다. 

 

Request 스코프 빈은 프로토타입 빈처럼 @Scope 어노테이션을 통해 지정한다. Request 스코프를 지정한 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);
    }
}

위에 설명했듯이 request 스코프 빈은 컨테이너를 통해 생성과 소멸이 관리되기 때문에 @PostConstruct, @PreDestroy 를 통해 초기화, 소멸 메소드를 정의했다. 각각의 메소드는 단순하게 빈이 생성되고 종료되는 지점을 출력한다.

 

log 메소드를 통해 HTTP 요청이 이루어진 URL 과 매개변수로 전달받은 메세지를 출력한다. 이 빈을 사용하는 클래스는 LogDemoController 와 LogDemoService로 다음과 같이 작성했다.

@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("user");

        return "OK";
    }
}
@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final MyLogger myLogger;

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

두 클래스에서 모두 리퀘스트 스코프의 MyLogger을 생성자 주입을 통해 의존관계를 주입받고 사용한다. 여기까지 작성하고 코드를 실행해 보았다.

 

오류가 터진다. 이유가 무엇일까?

 

원인은 request 스코프 빈을 생성자를 통해 주입받으려고 했기 때문이다. 생성자 주입은 빈이 생성될 때 의존관계를 주입한다. Request 스코프 빈은 HTTP 요청이 있을 때 생성되어 요청이 종료될 때 소멸되는 빈인데, 이를 무시하고 HTTP 요청 없이 마음대로 빈을 생성하려고 했기 때문에 오류가 생긴 것이다. 그렇다면 어떻게 HTTP 요청이 있을 때만 빈을 생성할 수 있을까? 전에 프로토타입 빈을 공부하며 사용했던 Provider를 통해 가능하다. 

 

 

스코프 Provider

사용법은 프로토타입 빈에서와 동일하다. 각각의 컨트롤러와 서비스 빈에서 생성자 주입받는 MyLogger를 ObjectProvider 로 감싸고 getObject() 메소드를 통해 필요할 때마다 빈을 반환받는다.

Controller에서 ObjectProvider를 통해 MyLogger 빈을 필요할 때 반환 받음

이렇게 하면 정상적으로 어플리케이션이 실행되는 것을 확인할 수 있다. 이렇게만 해도 편리하게 Request 스코프 빈을 다룰 수 있지만 더 편한 방법이 있다. 바로 프록시 방식을 사용하는 것이다.

 

 

- 스코프와 프록시

방법은 아주 간단하다. 기존에 @Scope 를 통해 request 스코프로 설정했던 어노테이션에 proxyMode 옵션을 추가하면 된다. 예시 코드는 아래와 같다.

proxyMode 를 ScopedProxyMode.TARGET_CLASS 로 설정

그 후 ObjectProvider 의존하는 모든 코드를 제거하고 이전 생성자 주입을통해 MyLogger를 주입받는 코드로 돌아가면 된다. 그 후 실행하면 정상작동한다. 어떻게 이 것이 가능한 것일까?

 

개발 공부를 하다보면 proxy라는 용어가 정말 많이 등장하는데, 단순하게 영어 뜻은 '대리인', '대용물' 을 뜻한다. 이 이름처럼, proxyMode 옵션을 통해 다음을 추가하면 MyLogger 클래스를 상속받는 가짜(대리) 클래스를 생성해 MyLogger 대신 생성자 주입 때 가짜 클래스를 주입한다.( 이 클래스는 CGLIB 라이브러리에 의해 생성된다. 이와 같이 클래스를 바이트조작을 통해 새로운 클래스를 만드는 것을 @Configuaration이 싱글톤 구현을 위해 동작하는 것에서 본적있다.)

 

이렇게 주입된 가짜 프록시 객체는 싱글톤 빈처럼 사용되고, 진짜 HTTP 요청이 있을 경우 내부에서 진짜 MyLogger 빈을 생성하여 사용한다. HTTP 요청이 종료되면 다시 컨테이너에 프록시 객체로써 존재한다.

 

중요한 점은 객체를 처음에 모두 만들어 두는 것이 아니라 진짜 조회가 필요한 시점까지 생성을 미룰 수 있다는 것이다.

 

하지만 리퀘스트 스코프도 프로토타입 스코프와 마찬가지로 최소화 해서 꼭 필요한 곳에서만 사용해야한다.


[참고 강의 출처]

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com