Spring/Spring Core

웹 스코프

민철킹 2021. 3. 29. 22:10

웹 스코프는 웹 환경에서만 동작하고, 스프링이 해당 스코프의 종료시점까지 관리하므로 종료 메서드가 호출된다.

 

웹 스코프 종류

  • request : HTTP요청 하나가 들어오고 나갈 때까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
  • session : HTTP Session과 동일한 생명주기를 가지는 스코프
  • application : 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프
  • websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프웹 환경에서만 동작하고, 스프링이 해당 스코프의 종료시점까지 관리하므로 종료 메서드가 호출된다.

클라이언트 A와 B는 서로 다른 빈 인스턴스를 가지게된다.

 


request 스코프 만들기

웹 환경 추가

웹 스코프는 웹 환경에서만 동작하므로 web 환경이 동작하도록 라이브러리를 추가하자.

implementation 'org.springframework.boot:spring-boot-starter-web'

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

스프링 부트는 웹 라이브러리가 없으면 AnnotationConfigApplicationContext를 기반으로 애플리케이션을 구동한다. 웹 라이브러리가 추가되면 웹과 관련된 추가 설정과 환경들이 필요하므로

AnnotationConfigServletWebServerApplicationContext를 기반으로 애플리케이션을 구동함.


동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다.

이럴 때 사용하기 좋은 것이 request 스코프이다.

UUID를 사용하여 HTTP요청을 구분하자. requestURL 정보도 추가하여 어떤 URL을 요청해서 남은 로그인지 확인 ==> 기대하는 공통 format : [UUID] [requestURL] {message}

(UUID는 각 사용자마다 가지는 고유 식별자이다.)

 

로그를 출력하기 위한 MyLogger 클래스

request스코프로 지정하여 해당 빈은 HTTP 요청 당 하나씩 생성되어 요청이 끝날 때 소멸된다.

@PostConstruct를 통하여 초기화가 될때 uuid에 값을 저장해둔다.

request스코프는 빈이 소멸될 때까지 스프링이 관리하므로 종료 메서드가 호출된다.

requestURL은 이 빈이 생성되는 시점에는 알 수 없으므로, 외부에서 setter로 입력받음

 

이제 Controller와 Service를 만들자.

현재는 View가 없으므로 @ResponseBody를 통해 요청을 응답한다.

HttpServletRequest를 사용하면 요청받은 값을 가져올 수 있다.

예를들어, id / password 등의 데이터를 컨트롤러로 보냈을 때 HttpServletRequest 객체안에 모든 정보가 다 들어가게 된다. 이를 통해 요청이 들어온 URL을 가져옴

 

비즈니스 로직이 있는 서비스 계층에서도 로그를 출력해보자.

여기서 중요한점이 있다. request scope를 사용하지 않고 파라미터로 이 모든 정보를 서비스 계층에 넘긴 다면, 파라미터가 많아서 지저분해진다.

더 문제는 requestURL 같은 웹과 관련된 정보가 웹과 관련없는 서 비스 계층까지 넘어가게 된다.

웹과 관련된 부분은 컨트롤러까지만 사용해야 한다. 서비스 계층은 웹 기술에 종속되지 않고, 가급적 순수하게 유지하는 것이 유지보수 관점에서 좋다.

request scope의 MyLogger 덕분에 이런 부분을 파라미터로 넘기지 않고, MyLogger의 멤버변수에 저 장해서 코드와 계층을 깔끔하게 유지할 수 있다.

 

하지만 위의 코드는 문제가 있다. MyLogger는 request 스코프 빈이다. 고객의 request가 발생해야만 생성이 되는데, 스프링 애플리케이션을 실행시키면 

컨트롤러와 서비스에서 의존관계 자동 주입이 수행되지만, 스프링 컨테이너를 초기에 띄우는 단계에서는 고객의 request가 발생할 수 없어 MyLogger가 생성되지 않기 때문에 오류가 발생한다.

이를 어떻게 해결할 수 있을까?


1. 스코프와 Provider

앞서 배운 ObjectProvider를 사용하면 이 문제를 해결할 수 있다.

의존관계주입(DI)가 아닌 필요한 의존관계를 찾는 DL

이제 매핑시킨 localhost:8080/log-demo로 접속해보자.

ResponseBody를 통해 반환시킨 OK가 출력되고 콘솔창을 열어보면

접속함에 따라 해당 유저의 UUID값과 requestURL, message가 출력되고 있는 것을 확인할 수 있다.

새로고침을 하면 새로운 UUID와 request 스코프 빈이 생성된다.

ObjectProvider 덕분에 ObjectProvider.getObject() 를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다.

ObjectProvider.getObject() 를 호출하시는 시점에는 HTTP 요청이 진행중이므로 request scope 빈의 생성이 정상 처리된다.

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

핵심은 다른 request에 대해서 다른 request scope빈이 생성되고 관리된다는 것이다. 요청이 아무리 많거나 동시에 들어와도 각각의 빈을 관리하기 때문에 구분할 수 있다!!!!

 

 

 

2. 스코프와 프록시

 

Scope 애노테이션에 proxyMode 속성을 추가하고 Controller와 Service를 이전에 request 스코프 빈을 DI하여 오류가 발생했던 코드로 되돌리고 아까와 같은 log-demo로 접속해본다.

Provider를 사용했을 때와 똑같이 동작한다. 어떻게 가능할까?

여기가 핵심이다.

적용 대상이 인터페이스가 아닌 클래스라면 TARGET_CLASS를

적용 대상이 인터페이스라면 INTERFACES를 선택

 

이렇게하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다. 마치 Provider처럼 동작한다.

 

myLogger 객체를 직접 출력해보면 

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

  • @Scope의 proxyMode = ScopedProxyMode.TARGET_CLASS 를 설정하면 스프링 컨테이너는 CGLIB이라는 바이트 코드를 조작하는 라이브러리를 사용하여 MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
  • 이를 스프링 컨테이너에 myLogger라는 이름으로 가짜 프록시 객체를 등록한다.
  • ac.getBean으로 조회를 해도 프록시 객체가 조회됨.
  • DI또한 가짜 프록시 객체가 주입

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

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

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

 

Provider, 프록시 둘다 핵심은 진짜 객체를 조회를 꼭 필요한 시점까지 지연처리 한다는 점!!

단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다.

 

웹 스코프가 아니어도 프록시는 사용할 수 있다.

이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워 진다.

반응형