진짜 개발자
본문 바로가기

FrameWork/Spring MVC

SpringMVC - Spring MVC 동작원리 - 5 (ViewResolver, DispatcherServlet 기본전략 등록 과정)

728x90
DispatcherServlet의 initViewResolver

SpringBoot나 이미 설정되어있는 Spring을 사용하신 분들은 위와같이 Controller의 handler들에서 간단히 view(문자열)이름만을 반환하면 사용자에게 해당 view가 보이게 되는 것을 경험하셨을 것입니다.

 

허나 이 포스팅의 처음부터 따라오신분들께서는 위와 같이 코드를 작성하고, 요청 한다면, 404 ERROR를 내뱉게 됩니다. 즉 view의 전체 경로 (/WEB-INF/hello.jsp)를 적어주어야 합니다. 그렇다면 어떻게 위와 같이 간단히 view 이름만을 반환하여, 사용자가 원하는 view를 보여줄 수 있을까요?

 

 

 

1. DispatcherServlet의 ViewResolver

앞서 질문드렸던 답은 바로 ViewResolver입니다. ViewResolver는 사용자가 요청한 것에 대한 응답 view를 렌더링하는 역할을 합니다. 간단히 말씀드리면 view 이름으로부터 사용될 view 객체를 맵핑하는 역할을 합니다. 자세한 부분은 마지막 부분에 설명드리겠습니다. 여기서 궁금한 점이 생기실겁니다. 우리는 ViewResolver를 생성하지도 Bean으로 등록하지도 않았는데 언제 생성되어 처리되지?궁금증을 해결하신다면 직접 ViewResolver를 등록하고 SpringBoot에서 처럼 view이름만 적어주어 알맞은 view를 찾아 렌더링할 수 있도록 할 수도 있습니다.

 

앞선 포스팅에서 별도의 HandlerMapping, HandlerAdapter를 등록하지 않아도 기본적으로 DispatcherServlet에서 가지고 있는 HandlerMapping, HandlerAdapter에 의해서 사용자의 요청이 처리되는 것을 알아보았습니다.

 

ViewResolver 또한 마찬가지 입니다. 개발자가 DispatcherServlet에 별도의 ViewResolver를 등록하지 않는다면 DispatcherServlet에 등록되어있는 InternalResourceViewResolver를 사용합니다. 직접 확인해보도록 하겠습니다.

 

 

1.1 Default ViewResolver 사용(InternalResourceViewResolver)

InternalResourceViewResolver가 등록되어 사용되는 과정을 살펴보도록 하겠습니다. 이 InternalResourceViewResolver는 기본적으로 jsp를 지원한다는 특징이 있습니다.

 

1.1.1 설정

web.xml

web.xml 파일에는 이전 포스팅과 내용이 같습니다. 

 

 

WebConfig Class

WebConfig파일에는 아무런 설정이 되어있지 않습니다.

 

 

HelloController Class

Controller class의 Handler 메소드에는 응답할 view의 전체 경로 및 확장자를 포함하도록 했습니다. 또한 view파일은 WEB-INF 디렉토리 하위에 위치하도록 했습니다.

 

 

1.1.2 Debugging

아시다시피 Servlet(DispatcherServlet에 국한된 내용이 아닙니다.)은 생명주기를 가지고 있는데요, 그 중 init()의 경우 Servlet이 생성될때 최초에 한번만 초기화를 거친 후 이 후 요청부터는 init()을 호출하지 않습니다.

 

<DispatcherSerlvet의 initStrategies() 메소드>

DispatcherServlet에서는 이러한 점을 이용하여 DispathcerServlet이 동작하기 위해 필요한 모든 Bean들을 이 init()생명주기에서 모두 호출하도록 했습니다. 바로 위 그림이 동작하기위해 필요한 모든 init 메소드 입니다. 단 한번만 호출되기 때문에 효율적으로 동작하게 됩니다. 많은 init메소드가 존재합니다. 저희가 필요한 것은 initViewResolvers(context);메소드 입니다. 저곳에 디버그 포인트를 주고 debugging을 해보겠습니다.

 

debugging point를 잡아준 상태에서 debugging 을 실행하고 localhost:8080/app/sample로 요청을 보내었습니다. 위와 같이 debugging point에 걸리는것을 볼 수 있습니다. step into(F7)를 클릭하여 해당 메소드를 더 자세히 보도록 하겠습니다.

 

<DispatcherServlet의 initViewResolvers() 메소드>

DispatcherServlet의 initViewResolvers() 메소드의 역할은 개발자가 등록한 ViewResolver 타입의 Bean을 모두 찾아와 viewResolvers 변수에 담습니다. 하지만, 별도로 등록한 ViewResolver Bean이 없다면, getDefaultStrategies() 메소드를 이용하여 기본으로 가지고 있는 ViewResolver를 가져옵니다.

우리는 아직 아무런 ViewResolver를 등록하지 않았기 때문에 viewResolvers변수가 null일 것이고, 그 때문에 getDefaultStrategies() 메소드가 실행되어 기본 ViewResolver인 InternalResourceViewResolver가 반환될 것입니다. 우선 getDefaultStretegies() 메소드를 조금 살펴보겠습니다.

 

*Java Reflection?

이 부분은 Java의 Reflection에 대한 지식이 약간 요구됩니다. 간단히 말씀드리면 class의 이름을 통해 해당 class를 가져와 메소드를 실행하거나 클래스로 만들 수 있습니다. 즉 런타임에 객체를 필요에 의해 생성할 수 있습니다.

 

<DispatcherServlet의 getDefaultStrategies() 메소드>

다시 getDefaultStrategies() 메소드를 살펴봅시다. 우선 strategyInterface라는 매개변수를 전달 받습니다. Class 형태로 전달을 받는데요, 이는 Handler, Adapater, ViewResolver, Theme, 등등의 초기화 작업에서도 똑같이 getDefaultStrategies를 이용하는데, 이때 필요한 class의 이름을 가져와 그에 맞는 객체를 생성하는등의 반복되는 코드들을 reflection을 이용하여 반복코드를 줄이기 위해 짜여진 코드입니다.

첫줄에서 매개변수로 전달받은 strategyInterface의 이름을 얻어옵니다. 저희는 initViewResolvers()메소드로부터 이 메소드가 호출되었기 때문에 ViewResolver가 담겨있을 것입니다.

<DispatcherServlet의 defaultStrategies>

둘째 줄에서는 이렇게 얻어온 key를 이용해 defaultStrategies라는 곳에서부터 class 이름을 얻어옵니다. 바로 위 그림을 보시면 defaultStrategies에 담겨있는 여러 기본 전략 객체들이 보입니다. 이곳에서 저희가 매개변수로 받은 ViewResolver라는 이름을 이용해 InternalResourceViewResolver class의 이름을 얻어오는 것입니다.

 

이제 넷째줄 에서 앞서 말씀드린 java의 reflection을 이용해 클래스 정보를 메모리에 로드하게 됩니다. 그 후 다섯번째 줄에서 createDefaultStrategy() 메소드를 이용해 해당 class이름의 객체를 생성하여 List에 담아 반환하게 됩니다. 이제 debugging을 끝내고, viewResolvers변수를 찾아봅시다.

 

<DispatcherServlet의 viewResolvers>

예상한대로 InternalResourceViewResolver가 등록되어있는것을 볼 수 있습니다.

 

 

 

1.2 직접 ViewResolver를 등록하기

이번에는 직접 ViewResolver를 등록하여 사용해보겠습니다. 여기서 맨처음 질문했던 어떻게 view이름만 가지고 사용자가 요청한 view를 찾아서 response해주는지를 알 수 있습니다.

 

1.2.1 설정

WebConfig Class

우선 WebConfig class에 ViewResolver Bean을 등록해줍니다. 여기서 잘 보셔야 하는 부분이 바로 suffix, prefix입니다. Spring Boot에서 여러분이 view이름만 적어주어도 알맞은 view를 찾아가는 이유가 바로 이것입니다. InternalResourceViewResolver에서는 요청에 맞는 View를 렌더링하여 반환해주는 역할을 한다고했습니다. 여기서 Prefix는 렌더링시 handler에서 반환하는 문자열의 앞에 Resolver가 자동으로 붙혀줄 문자열을 의미합니다. Suffix는 당연히 뒷쪽에 붙는 문자열입니다. 따라서 이처럼 설정하게 되면 단순히 handler에서 view 이름만을 반환하면 ViewResolver에서 view의 위치와 확장자를 연결하여 view를 렌더링하게 됩니다.

 

HelloController Class

prefix, suffix 설정을 했으니 당연히 handler에서 반환하는 문자열도 위와 같이 변경해주어야 합니다.

 

 

1.2.2 Debugging

앞서서 debugging을 자세히 해봤으므로 조금 간단히 진행하겠습니다.

 

이번에는 initViewResolvers()에서 개발자가 등록해준 ViewResolver가 있으므로 viewResolvers 변수가 null이 아님을 알 수 있습니다. 그렇기 때문에 getDefaultStrategies() 메소드는 호출되지 않을 것입니다.

 

해당 객체를 보면 prefix, suffix에 우리가 입력해준 값이 있는것을 볼 수 있습니다.

 

 

1.3 결과