스프링/SpringBasicCore

Section9-2 빈 스코프(웹 스코프)

나는웅쓰 2024. 3. 25. 15:29
인프런 강의 中 
김영한 강사님의 '스프링 핵심 원리' 내용을 정리했습니다.

지난 시간에 하나의 인스턴스로 운영되는 싱글턴 빈과 호출을 할 때마다 새로 생성되는 프로토타입 빈에 대해서 학습해 보았다. 이번 시간에는 웹 환경에서 동작하는 웹 스코프에 대해서 김영한 강사님의 강의를 들으며 학습해보자!

 

목차

  • 빈 스코프란?
  • 프로토타입 스코프
  • 프로토타입 스코프 - 싱글톤 빈과 함께 사용 시 문제점
  • 프로토타입 스코프 - 싱글톤 빈과 함께 사용 시 Provider로 문제 해결
  • 웹 스코프
  • request 스코프 예제 만들기
  • 스코프와 Provider
  • 스코프와 프록시

 

1. 웹 스코프

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

 

웹 스코프 종류

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

넷 다 범위만 다르지 동작 방식은 비슷하다.

 

request 스코프 예제 만들기

우리는 request를 사용해 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리되는지 logger를 사용해 확인해보겠다. 테스트 결과가 어떻게 나와야 되는지 미리 얘기하면, 우리가 사용할 Controller, Service layer에서 같은 HTTP 요청일 땐 같은 빈 주소를 log로 출력시키면 원하는 결과가 나온 것이다.

 

bulid.gradle에 추가

web 라이브러리 추가

 

build.gralde에 위 코드를 추가하고 Run하면 8080 port가 시작됐다는 실행 코드가 나타날 것이다.(그럼 성공)

 

참고

spring-boot-starter-web 라이브러리를 추가하면 스프링 부트는 내장 톰캣 서버를 활용해 웹 서버와 스프링을 함께 실행시킨다.

스프링 부트는 웹 라이브러리가 없으면 AnnotationConfigApplicationContext를 기반으로, 웹 라이브러리가 있으면 AnnotationConfigServletWebServerApplicationContext를 기반으로 애플리케이션을 구동한다.

 

 

 

(1). 스코프와 Provider

 

MyLogger(request 스코프 log를 찍어보기 위한 코드)

MyLogger 코드

 

  • UUID를 사용해 HTTP 요청을 구분하고, 어떤 URL을 통해 요청이 들어왔는지 requestURL을 통해 확인하는 코드이다.
  • @Scope(value = "request")를 사용해서 request 스코프로 지정했다. 이제 이 빈은 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.
  • 이 빈이 소멸되는 시점에 @PreDestory를 사용해서 종료 메시지를 남긴다.
  • requestURL은 이 빈이 생성되는 시점에는 알 수 없으므로, 외부에서 setter로 입력 받는다.

LogDemoController

Controller layer

 

  • 고객 요청 정보를 받기 위해 HttpServletRequest를 사용했고, 정보 중 requestURL을 추출해 setRequestURL로 MyLogger의 requestURL필드에 값을 삽입했다.
※ ObjectProvider(프로토타입 스코프)를 사용하지 않으면 에러가 발생한다!!

너무나도 간단한 이유다. request 스코프 빈은 HTTP 요청이 시작되야 생성된다. 그런데 아무런 요청도 없이 서버만 Run을 하게 되면 당연히 빈이 생성되지 않아 자동 의존 주입을 할 수 없다. 그래서 에러가 발생한다. 그럼 어떻게 해야되느냐?! 바로 이전 글에서 다루었던 프로토타입 스코프인 ObjectProvider를 사용해 껍데기 빈을 주입시켜 놓은 후, 요청이 들어올 때 빈을 생성해서 사용하면 된다!! 그래서 위 코드에서 ObjectProvider를 사용한 것이다.

 

LogDemoService

Service layer

  • Service layer에서도 위와 같은 이유로 ObjectProvider를 사용했다.

테스트 결과

테스트 결과(성공)

  • ObjectProvider 덕분에 ObjectProvider.getObject()를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다.
  • ObjectProvider.getObject()를 호출하는 시점에는 HTTP 요청이 진행중이므로 request scope 빈의 생성이 정상 처리된다.
  • ObjectProvider.getObject()를 LogDemoController, LogDemoService에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다. 

 

 

(2). 스코프와 프록시

 

프록시는 말 그대로 대리!! ObjectProvider와 비슷하게 아직 생성되지 않은 빈을 대신해 대리(가짜) 빈을 주입시켜 놓고 사용할 빈이 생성되면 그때 호출해서 사용한다.

 

MyLogger

log 코드

 

  • proxMode = ScopedProxyMode.TARGET_CLASS를 추가
    • 적용 대상이 인터페이스가 아닌 클래스면 TARGET_CLASS
    • 적용 대상이 인터페이스면 INTERFACES
  • 위 코드를 추가하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.

 

LogDemoController

Controller layer

 

LogDemoService

Service layer

  • MyLogger 클래스에 proxyMode를 적용했기 때문에 Controller와 Service layer에서 MyLogger를 생성자 주입 코드로 작성하면 된다.(즉, MyLogger를 사용하는 layer에서 따로 코드를 건드릴 필요가 없음)

 

번외) 웹 스코프와 프록시 동작 원리

myLogger.getClass()로 myLogger를 확인해보면

myLogger.getClass() 결과

 

결과를 확인해보면 $$SpringCGLIB$$라는 가짜 프록시 객체가 주입되어 있는 것을 확인할 수 있다.

 

가짜 프록시 객체 주입 원리

  1. @Scope의 proxyMode = ScopedProxyMode.TARGET_CLASS)를 설정하면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
  2. 위 결과처럼 우리가 등록한 순수한 MyLogger 클래스가 아니라 MyLogger$$SpringCGLIB$$라는 클래스로 만들어진 객체가 대신 등록되는 것을 확인할 수 있다.
  3. 그리고 스프링 컨테이너에 "myLogger"라는 이름으로 진짜 대신에 이 가짜 프록시 객체를 등록한다.
  4. ac.getBean("myLogger", MyLogger.class)로 조회해도 프록시 객체가 조회되는 것을 확인할 수 있다.

 

가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.

  • 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있다.
  • 클라이언트가 myLogger.log()을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것이다.
  • 가짜 프록시 객체는 request 스코프의 진짜 myLogger.log()를 호출한다.
  • 가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다.(다형성)

프록시 동작 정리

  • CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
  • 이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤 처럼 동작한다.

프록시 특징 정리

  • 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.
  • Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.
  • 단지 어노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 장점이다.
※ 주의!

- 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다.
- 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워진다.

 

정리를 마치며..

멋진 기술 블로그들을 보며, 나도 나중에 꼭 기술 블로거가 돼야지!라는 소망이 있었다. 기술 블로그라고 하기엔 김영한 강사님의 강의를 정리한 수준밖에 안되지만 그래도 멘토링 덕분에 기술 블로그에 첫걸음을 내디딜 수 있었던 것 같아서 정말 뜻깊은 시간이었다!!

공부한 것들을 정리하며 남들에게 도움이 될 수 있는 그런 블로거로 성장하는 모습을 다들 기대해주십쇼!

 

!!김영한 강사님 강의

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

 

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

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

www.inflearn.com