1. 웹 스코프
웹스코프의 특징과 종류
스프링은 주로 웹 기술에 적용되므로, 웹과 관련된 스코프가 따로 지정되어 있다. 웹 스코프는 웹 환경에서만 동작한다는 특징이 있다. 웹 스코프의 종류는 다음과 같다.
- request : HTTP 요청 하나가 들어오고 나갈 때까지 유지된다. 각 HTTP 요청마다 개별적으로 인스턴스가 생성되고 관리된다.
- session : HTTP session과 동일한 주기를 갖는다.
- application : 서블릿 컨텍스트와 동일한 주기를 갖는다.
- websocket : 웹 소켓과 동일한 주기를 갖는다.
각 스코프는 생명주기만 다르고, 동작 방식은 거의 같다. 강의에서는 request 스코프를 예제로 학습해본다.
웹 스코프 연습해보기 : 웹 Logger 만들기
웹 스코프는 프로토타입 스코프와 마찬가지로, 여러 명의 클라이언트 요청에 대해서 개별적인 빈을 반환하게 된다. 하지만 HTTP 요청이 나갈 때까지 유지되므로, 종료 메서드도 실행된다는 점이 프로토타입 스코프와 다르다. 시험을 위해서 웹 요청이 들어올때마다 요청 내용이 작성되는 Logger를 만들어보자.
웹 환경 구축을 위해 웹 라이브러리를 build.gradle에 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-web'
이렇게 하면, 스프링 부트가 내장 톰캣 서버를 구동하여 웹 서버와 스프링을 함께 실행시키게 된다. 그리고 웹 라이브러리가 없을 때는 AnnotationConfigApplicationContext를 기반으로 애플리케이션을 구동하지만, 웹 라이브러리가 추가되면 추가 환경 설정을 위해서 AnnotationConfigServletWebServerApplicationContext를 기반으로 애플리케이션을 구동하게 된다.
그리고 main 메서드가 있는 CoreApplication.class를 실행하면, tomcat 웹서버가 동작하고, localhost:8080 접속 시 Whitelabel Error Page가 나타난다.
다음으로 Log를 작성하는 MyLogger 클래스를 만든다.
@Scope(value = "request")로 설정했기 때문에, request 웹스코프에 해당하게 된다. 클라이언트의 요청이 와서 MyLogger 빈이 생성되면, @PostConstruct에 의해 초기화가 진행되어 uuid 필드값을 랜덤으로 생성하여 저장하게 된다. 그리고 HTTP 요청이 끝나게 되면, 종료 메서드가 @PreDestroy에 의해 실행되어 빈은 소멸되고, 종료 메시지가 출력되는 구조이다. 요청에 대한 URL 정보는 빈이 생성될 때 알 수 있는 부분이 아니므로, setter를 추가하여 외부에서 해당 빈에 requestURL를 set할 수 있도록 한다.
※ UUID.randomUUID()는 범용 고유식별자(universally unique identifier)라고 불리며, 128비트의 숫자로 32자리의 16진수로 표현된다. 매우 길기 때문에 영어 단어 의미대로 중복될 확률이 매우 희박한 번호를 부여할 수 있게 된다.
이제 클라이언트로부터 요청이 오면, Controller와 Service에서 MyLogger를 받아 처리하고 정보를 출력하도록 해보자. MyLogger를 만들지 않고, 사용자의 요청을 Controller에서 받은 다음, 받아온 정보(URL, UUID 등)들을 그냥 바로 Service 코드로 넘기면 웹과 관련된 정보가 모두 Service쪽으로 넘어가게 된다. 이런 구조로 만들게 되면 서비스 계층도 웹에 종속되므로 유지보수에 좋지 않다. 따라서 MyLogger 빈을 request scope로 만들어서 필요한 정보만 service로 넘기고, http 요청이 끝나면 해당 빈도 종료되도록 해주는 것이다.
Controller에서는 @RequestMapping을 통해 "log-demo" 주소로 오는 요청을 처리하도록 한다. 그리고 @ResponseBody로 단순 String을 응답으로 처리하도록 한다. 그리고 형식 뿐이지만, LogDemoService에서의 logic() 메서드를 호출한다.
Service 코드에서도 MyLogger를 주입받고, MyLogger의 log() 메서드를 호출하도록 한다.
이렇게 작성 후 애플리케이션을 실행하면, 다음과 같은 오류가 발생한다. 이것은 @Scope(value = "request")로 등록된 myLogger 빈이 의존관계 주입을 위해서 컨테이너에서 불러와져야되는 상황인데, request 스코프에 해당하는 빈은 http 요청이 있어야만 생명주기가 시작되기 때문이다. 여기서는 아직까지 실제 http 요청이 전혀 없으므로 에러가 발생한 것이다.
컨테이너에서 myLogger 빈을 주입받는 과정을 스프링이 실행되고 의존관계가 주입되는 단계가 아니라, 실제 고객 요청이 올 때로 미뤄야한다(lazy loading). 이것을 위해 앞서 배운 Provider를 적용한다.
2. Provider, 프록시 적용
Provider 적용
ObjectProvider를 myLogger를 주입받는 Controller와 Service 코드에 모두 적용한다.
이후 localhost:8080/log-demo에 접속해보면, 콘솔창에 의도한대로 로그가 출력되는 것을 확인할 수 있다. 새로고침 할때마다, 즉 클라이언트의 요청이 올때마다 다른 myLogger 객체가 호출된다.
프록시 객체 적용
Provider를 써서 코드를 번잡하게 할 필요없이 myLogger 객체를 지연해서 가져올 수 있는 방법이 프록시 객체화 하는 것이다. Controller와 Service 코드는 ObjectProvider를 적용하기 전으로 돌려놓고, MyLogger 클래스는 다음과 같이 ProxyMode를 적용해준다. TARGET_CLASS는 해당 클래스를 프록시 객체화하겠다는 의미를 나타낸다.
이후 작동하면 Provider를 적용했을때와 마찬가지로 정상작동하는 것을 확인할 수 있다. 그리고 Controller나 Service 코드에서 myLogger.getClass()를 출력해서 myLogger 빈의 클래스를 확인해보면 CGLIB를 적용한 객체임을 알 수 있다.
즉, ProxyMode를 적용하면 스프링이 CGLIB 라이브러리로 myLogger 클래스를 상속받은 가짜 프록시 객체를 만들어서 주입하는 것이다. 이 프록시 객체는 요청이 오면 원래 빈을 반환하는 방식으로 원본 객체를 호출하게 된다. 그리고 가짜 객체이므로 실제 request scope와는 상관이 없고 싱글톤 빈처럼 작동한다. 또한 웹 스코프가 아니더라도 프록시를 적용할 수 있음을 기억하자.
Provider, Proxy는 실제 객체의 조회가 필요한 시점까지 지연처리할 수 있다는 점이 중요하다. 코드를 고치지 않고 어노테이션의 설정 변경만으로 원본 객체를 프록시 객체로 대체하였다. 이것이 다형성과 DI 컨테이너가 가진 큰 장점이라고 할 수 있다.
참조
1. 인프런_스프링 핵심 원리 기본편_김영한 님 강의
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
'Programming-[Backend] > Spring' 카테고리의 다른 글
[스프링 웹MVC] 2. 프로젝트 생성과 웹서블릿 기본 개념 (0) | 2021.07.18 |
---|---|
[스프링 웹MVC] 1. 웹 애플리케이션 이해 (0) | 2021.07.17 |
[스프링 기초] 16. 빈 스코프 : Prototype Scope와 Provider (0) | 2021.07.11 |
[스프링 기초] 15. 빈 생명주기 콜백 : initMethod, destroyMethod, @PostConstruct, @PreDestroy (0) | 2021.07.11 |
[수정중][스프링 기초] 14. 여러 개의 빈이 있을 때 자동주입하기, @Primary, @Qualifier, NoUniqueBeanDefinitionException (0) | 2021.07.07 |