진짜 개발자
본문 바로가기

FrameWork/Spring MVC

SpringMVC - Spring MVC 동작원리 - 4(DispatcherServlet의 ResponseBody 응답 과정, HandlerMapping, HandlerAdapter)

728x90
Spring - Spring MVC 동작원리 - 3(DispatcherServlet이란 - 2)

DispathcerServlet이란? 2번째 포스팅입니다. 저번 시간에는 Servlet Project에 DispatcherServlet을 매핑하고, 모든 요청을 DispatcherServlet에서 우선적으로 받도록 했고, DispatcherServletIoC Container를 연동하는 방법을 알아보았습니다.

 

이번 포스팅에서는 그러한 DispatcherServlet에서 모든 요청을 받아들여서 어떻게 요청에 맞는 Handler들을 호출하고 그 응답을 사용자에게 전달하는지 그중에서도 ResponseBody로 응답을하는 과정을 알아보도록 하겠습니다.

 

 

1. DispatcherServlet의 요청 처리과정

DispatcherServlet에서는 모든 요청을 받아 각각의 Handler로 요청을 보내어 처리를 하게 됩니다. 이러한 과정들을 Debugging해보며 자세히 알아보도록 하겠습니다. 우선 DispatcherServlet의 주요한 기능을 하는 곳에 DebuggingPoint를 미리 체크해 두었습니다.

 

Debugging을 시작해봅시다. debugging을 하게 되는 경우 페이지의 root에 요청을 하게 될 것입니다. 즉 http://localhost:8080/의 요청을 보내게 됩니다.

 

1.1 doService()

<DispatcherServlet의 doService() 메소드>

첫째로 모든 요청은 적절한 HttpServletRequest, HttpServletResponse 객체를 매개변수로 받는 doService()를 호출하게 됩니다. 주석을 읽어보면 "DispatcherServlet 관련한 특성을 표시하고, doDispatch()메소드에게 위임을한다." 라고 나와있습니다. 관련한 특성을 표시한다는 의미는 아직 못알아 듣겠습니다. doDispatch()에게 위임을 한다고 하니 doDispatch()메소드를 따라가 보겠습니다.

 

1.2 doDispatch()

<DispatcherServlet의 doDispatch() 메소드>

사용자가 요청한 값이 doService()를 통해서 doDispatch()로 왔습니다. 주석을 읽어봅시다. 실제로 핸들러에게 요청을 전달하는 처리를 한다고 나와있습니다. 여기에서 모든 요청을 각각의 알맞은 Handler에게 전달하는 역할을 하게됩니다. 조금 더 자세히 알아보겠습니다.

 

<DispathcerServlet의 doDispatch() 메소드>

doDispatch() 메소드의 중간쯤 입니다. getHandler() 메소드가 눈에 띕니다. 주석을 읽어보면 현재 요청에 대한 handler를 결정한다고 합니다. getHandler()메소드를 자세히 살펴보겠습니다.

(multiPartRequest 의 경우에는 동영상과 같은 커다란 파일을 업로드 하는 경우 이것을 분할하여 동시에 빠르게 업로드할 수 있도록 하는 기능 또는 전달 가능한 단위보다 큰 파일을 업로드할때 사용하는 기능입니다)

 

 

1.2.1 DispatcherServlet의 getHandler()

<DispatcherServlet의 getHandler() 메소드>

역시나 주석을 먼저 읽어보겠습니다. 이 요청을 위한 HandlerExecutionChain을 리턴한다고 나와있습니다. 먼저 HandlerExecutionChain이 무엇인지를 알아 보겠습니다.

 

HandlerExecutionChain에서는 간단히 말해 Object 타입으로 전달되는 handler를 원래의 Handler 타입에 맞게 변환하여 그 Handler를 전달하는 등의 일을 하고있습니다. 다시 getHandler()로 돌아가겠습니다.

 

<DispatcherServlet의 getHandler() 메소드>

getHandler()의 메소드의 역할은 간단합니다. for문을 돌며 DispatcherServlet이 가지고 있는 HandlerMapping객체들의 getHandler()메소드에 다시 request를 전달하여 request를 처리할 수 있는 HandlerExecutionChain을 찾아 반환하는 역할을 합니다.(DispatcherServlet은 요청을 처리할 수 있는 Handler를 찾기 위한 HandlerMapping 객체들을 리스트 형태로 가지고 있습니다.)

 

<DispatcherServlet의 getHandler() 메소드>

그렇다면 어떤 HandlerMapping 객체가 어떻게 선택이 될까요? 우선 앞서 말씀드렸듯, DispatcherServlet은 HandlerMapping 객체들을 List형태로 가지고 있습니다. 위 그림의 하단을 보시면 2개의 HandlerMapping 객체가 등록 되어있는 것을 볼 수 있습니다. 저희는 @을 이용해 Handler들을 생성했기 때문에 RequestMappingHandlerMapping이 선택될 것입니다. 이 과정을 보기 위해 다시 HandlerMapping 객체의 getHandler() 메소드를 살펴보겠습니다.

 

<AbstractHandlerMapping 객체의 getHandler() 메소드>

HandlerMapping객체의 getHandler()메소드에서는 어떤 일을 할까요? 주석을 읽어보면 주어진 request에 맞는 handler를 찾아주는 역할을 함을 볼 수 있습니다. 첫 줄을 보면 handler를 가져오기 위해 다시 getHandlerInternal()을 호출합니다. 너무 얽혀있네요.. getHandlerInternal() 메소드를 보겠습니다.

 

 

<AbstractHandlerMapping 객체의 getHandlerInternal() 메소드>

우선 첫줄에서 getUrlPathHelper().getLookupPathForRequest() 를 이용해 request객체로부터 사용자가 요청한 url을 파악합니다. 그 후 lookupHandlerMethod() 메소드에 찾아낸 url과, request를 함께 전달하여 호출하고 그 결과로 HandlerMethod를 얻어냅니다. lookUpHandlerMethod()가 어떻게 동작하는지 알아봅시다.

 

<AbstractHandlerMapping 객체의 lookupHandlerMethod() 메소드>

lookupHandlerMethod()의 주석에는 현재 request에 가장 적절히 매칭되는 handlerMethod를 찾는 역할을 한다고 적혀있습니다. 그렇다면 어떻게 찾는 걸까요 getMappingsByUrl() 메소드를 또 살펴보겠습니다.

 

<AbstractHandlerMapping 객체의 getMappingByUrl() 메소드>

HandlerMapping 객체에는 urlLookup이라는 맵이 있으며 이 맵에는 해당 Mapping 객체가 처리할 수 있는 url목록들이 들어있습니다. getMappingByURL() 메소드는 사용자가 요청한 url을 key값으로 해 이 urlLookup 맵에서 값을 찾습니다. 해당 맵의 값에는 List<V>자료형이 들어가 있는데 이는 한 url에 여러 Http method가 있을 수 있기 때문입니다.

 

<AbstractHandlerMapping 객체의 lookupHandlerMethod() 메소드>

다시 lookupHandlerMethod() 메소드입니다. 앞서 getMappingsByUrl() 메소드의 결과 값으로 반환된 매칭되는 method 들이 담긴 List를 List형의 directPathMatches변수에 담는 것을 볼 수 있습니다. 해당 변수가 가지고 있는 값을 보면 우리가 현재 HelloController 에 작성한 "GET /"이 담겨있음을 볼 수 있습니다.

 

<AbstractHandlerMapping 객체의 lookupHandlerMethod()>

이후 addMatchingMappings() 메소드를 통해서 해당 url에 매칭되는 진짜 Controller의 HandlerMethod를 찾게 됩니다. 결국 lookupHandlerMethod()는 이렇게 찾아낸 handlerMethod를 리턴하게 됩니다.

 

<AbstractHandlerMapping 객체의 getHandler() 메소드>

다시 DispatcherServlet의 getHandler()메소드는 앞서 찾아낸 Controller의 Method를 리턴하게 됩니다.

 

 

1.2.2 DispatcherServlet의 getHandlerAdapter()

<DispatcherServlet의 doDispatch() 메소드>

돌고돌아 다시 DispatcherServlet의 doDispatch() 메소드입니다. getHandler()메소드를 이용해 사용자의 요청을 처리할 Handler메소드를 얻어왔습니다. 이 후 doDispatch() 메소드에서는 이 handler를 처리할 HandlerAdapter를 찾기 위해 getHandlerAdapter()에 찾아낸 handlerMethod를 매개변수로 전달하여 호출하게 됩니다.

 

<DispatcherServlet의 getHandlerAdapter() 메소드>

getHandlerAdapter() 메소드에서는 인자로 전달받은 handlerMethod의 처리를 지원하는 adapter를 찾아서 리턴하는 역할을 합니다. DispatcherServlet에 등록되어있는 Adapter은 위그림 하단에 보이는 3가지 입니다. 한눈에 보아도 마지막원소인 RequestMappingHandlerAdapter이 선택될것 같습니다.

 

<DispatcherServlet의 doDispatch() 메소드>

예상대로 ha는 RequestMappingHandlerAdapter가 선택되었습니다. 계속해서 doDispatch()메소드의 로직을 따라가보겠습니다.

 

<DispatcherServlet의 doDispatch() 메소드>

이번에는 얻어낸 Adapater의 handle()메소드를 호출하고 있습니다. 주석을 보면 "실제로 handler를 호출하는 역할을 한다"고 나와있습니다. mv 에는 handle의 결과로 반환되는 ModelAndView객체를 담도록했지만 우리는 @RestController를 사용했기 때문에 생성되는 View가 없으므로 null값이 담길 것입니다. handle() 메소드를 조금 더 살펴 보겠습니다.

 

<AbstractHandlerMethodAdapter 의 handle() 메소드>

handle() 메소드에서는 다시 handleInternal()을 호출하고 있네요, handleInternal()을 살펴보겠습니다.

 

<RequestMappingHandlerAdpater의 handleInternal() 메소드>

앞서 찾아낸 RequestMappingHandlerAdapterhandleInternal() 메소드에서 중간쯤의 코드입니다. invokeHandlerMethod()를 다시 호출하고 있는데요 이 메소드는 java reflection을 이용하여 우리가 찾아낸 handlerMethod()를 실제로 실행하는 역할을 합니다.

 

<RequestMappingHandlerAdapter의 invokeHandlerMethod() 메소드>

invokeHandlerMethod() 메소드 입니다. 위의 복잡한 처리들은 생략하고 실제로 handler를 호출하는 부분을 보여드리기 위해 위 그림만을 캡쳐했습니다. 이곳에서 바로 사용자가 요청한 url에 맞은 Handler를 직접 호출하여 실행하도록 하는 부분입니다. 디버깅을 진행해보면??

 

드디어 Handler가 이 요청을 처리하게 됩니다. 저희는 @RestController를 사용하기 때문에 문자열이 반환이 됩니다.

 

<ServletInvocableHandlerMethod의 invokeAndHandle() 메소드>

invokeAndHandle() 메소드의 returnValue 에는 Handler의 처리결과인 index라는 문자열이 담기게 된것을 볼 수 있습니다.

 

1.2.3 ReturnValueHandler

<ServletInvocableHandlerMethod의 invokeAndHandle() 메소드>

이제 거의 다 왔습니다. invokAndHandle() 메소드에서 실질적으로 Handler 메소드를 호출을 한 뒤 이제는 사용자에게 보여질 Reponse를 생성해야 합니다. ReturnValueHandler는 사용자에게 응답하기 위해 만들어질 Response를 만들기 위한 handler들을 의미합니다.

 

<HandlerMethodReturnValueHandlerComposite의 handleReturnValue() 메소드와 selectHandler() 메소드>

handleReturnValue() 메소드에서 하는일은 간단합니다. 우선 HandlerMethodReturnValueHandlerComposite객체가 가지고있는 15개의 RetuenValueHandler중에서 handler의 호출 결과로 생성된 returnValue를 처리할 수 있는 것을 찾기 위해 selectHandler()를 호출합니다. 그 후 찾아낸 returnValueHandler의 handleReturnValue()메소드를 호출하여 적절한 Response를 생성하게 됩니다.

 



2. HandlerMapping

HandlerMapping에 대해 간략하게 알아보겠습니다. HandlerMapping은 사용자의 요청정보를 이용해 실제로 그 요청을 수행할 Handler를 찾아주는 역할을 합니다.

2.1 HandlerMapping의 Property

 order

이전에 알아보았듯 각각의 Bean은 여러개가 등록될 수 있습니다. 이때 각각의 순서를 정해주기 위한 프로퍼티입니다.


defaultHandler

요청한 url중 어떠한 handler와도 매핑되지 않는 경우 실행할 HandlerMapping을 지정할 수 있습니다. 에러메시지 또는 에러페이지를 설정할 때 사용합니다.


2.2 Handler Interceptor

Interceptor의 경우 DispatcherSerlvet이 Controller를 호출하기 전,후에 request, response, ModelAndView를 참조해 가공 및 처리를 할 수 있는 필터와 같은 역할을 합니다. 

Interceptor가 없는 경우 바로 Handler를 호출하게 되지만 하나 이상의 Interceptor가 등록된 경우 지정된 순서에 따라 Interceptor들을 거쳐간 후 Handler가 호출 됩니다.


구현

HandlerInterceptor는 HandlerInterceptor 인터페이스를 구현하므로써 생성할 수 있습니다. 구현 하는 메소드는 각각 preHandle(), postHandle(), afterCompletion() 등으로 모두 메소드의 이름 그대로의 역할을 수행합니다. 앞서 말씀드렸듯이 Request, Response, ModelAndView를 참조해 가공하는 역할을 하므로 매개변수에서 전달받는 것을 볼 수 있습니다. 당연하지만 ModelAndView의 경우는 핸들러를 호출한 뒤 생성되는 것이므로 postHandle에만 존재하는 것을 볼 수 있습니다.




 

3. 요약

- 사용자가 요청을 하게 되면 DispatcherServlet의 doService()가 호출됩니다.

- doService()에서는 이 요청을 처리하기 위핸 Handler와 Adapter를 찾아 호출하기 위해 다시 doDispatch()를 호출합니다.

- doDistpatch()에서는 HandlerMapping을 통해서 요청에 맞는 HandlerMethod를 찾습니다. 그 후 찾아낸 HandlerMethod를 호출할 수 있는 HandlerAdapter를 찾습니다.

- HandlerAdpater에 의해 실질적으로 handlerMethod가 실행됩니다.

- 마지막으로 ReturnValueHandler에 의해 handlerMethod의 실행 결과값을 적절한 Response로 생성하여 사용자에게 응답합니다.