JVM/Spring

[SPRING] 스프링 MVC 구조? (DispatcherServlet?)

Hyo Kim 2022. 4. 22. 00:11
728x90
반응형

🙃 서론

스프링은 정말 사용자가 사용하기 편한 기능들을 많이 제공해준다.

그 중에서도 컨트롤러는 정말 다양한 파라미터와 리턴 값을 사용자 뜻대로 이것저것 가져와서 사용할 수 있다.

 

예를 들어 파라미터로 String이나 Integer 로 받을 수도 있고, 직접 만든 DTO 객체로도 받을 수 있다.

또, 리턴 값으로 String으로 view 경로를 보낼 수도 있고, 진짜 String을 보낼 수도 있고, ModelAndView 를 보낼 수도 있다.

 

어떻게 Spring은 이렇게 다양하게 사용자 입맛대로 만들 수 있게 해주는지에 대해서 파악해보자.

스프링 MVC에서 받을 수 있는 인수 - 링크


😆 본론

Dispatcher Servlet

dispatcher servlet

일단, 가장 중요한 Dispatcher Servlet에 대해서 개념을 잡고 가야 합니다.

클라이언트에서 HTTP 요청을 할 때 우리가 선언한 컨트롤러를 바로 타는 게 아니라,

Dispatcher Servlet 라는 녀석이 모든 요청을 가로챕니다.

 

Dispatcher Servlet 은 다른 말로 Front Controller 라고도 부르는 데,

앞 단에 있는 컨트롤러라는 뜻으로 모든 요청을 먼저 받아서 다양한 기능들을 수행해주는 녀석이라고 보면 됩니다.


MVC 처리 정리

Spring MVC

1. 클라이언트에서 HTTP 요청을 보냅니다.

2. Dispatcher Servlet 이 모든 요청을 가로채갑니다.

3. Handler Mapping 에서 요청된 정보(URI, HTTP Method)와 일치하는, 등록된 핸들러 (컨트롤러 or 정적리소스)가 있는지 찾습니다.

4. 찾은 핸들러(컨트롤러)를 처리할 수 있는 핸들러 어댑터를 조회합니다.

5. 핸들러 어댑터를 통해 핸들러(컨트롤러)를 실행하여 ModelAndView 로 반환합니다.

6. ModelAndView 에 ViewName이 있다면, viewResolver에서 논리 이름으로 변경하여 View를 반환합니다.

  ex) "hello" -> "/WEB-INF/views/hello.jsp"

6-1. view 를 reander 하여 화면을 호출합니다.

 

전체적인 MVC 흐름을 간략하게 정리해보았습니다.

이 중에서도 핸들러 어댑터가 정말 다양한 일들을 하기 때문에, 핸들러 어댑터를 조금 더 살펴보겠습니다.


어댑터 패턴

먼저, 핸들러 어댑터를 알기 위해서는 어댑터 패턴을 알아야 합니다.

 

콘센트

나라마다 콘센트 규격이 다 다른 걸 알 수 있습니다.

어디서는 220v, 다른 곳에서는 110v 로 돼지코 모양이 다 다르게 생겼습니다.

 

어댑터

그럴 때 우리가 가지고 있는 선의 규격과 연결해야 하는 곳의 규격을 맞추기 위해 어댑터라는 녀석을 사용합니다.

 

어댑터 패턴도 위와 동일합니다.

여러 다른 처리 로직들을 공통으로 사용할 수 있게 끔 중간에서 변경해주는 패턴이라고 보면 됩니다.


핸들러 어댑터

핸들러어댑터

핸들러 어댑터 처리 정리

1. 찾은 핸들러 어댑터의 handle() 메소드를 실행시킵니다.

2. ArgumentResolver 를 통해 호출할 핸들러(컨트롤러)의 arguments 를 만듭니다.

  2-1. 이때 @RequestBody 가 붙어있거나, HttpEntity를 받을 경우 HTTP 메시지 컨버터를 호출합니다.

3. 어댑터에서 만든 arguments 를 넘겨서 핸들러(컨트롤러)를 호출합니다.

4. ReturnValueHandler 를 통해 컨트롤러의 리턴 값을 알맞게 변경합니다.

  4-1. 이때 @ResponseBody가 붙어있거나, 리턴 값이 HttpEntity일 경우 HTTP 메시지 컨버터를 호출합니다.

5. ModelAndView 혹은 null 을 DispatcherServlet 으로 리턴합니다.

 

* HTTP 메시지 컨버터

Spring은 기본적으로 메시지 컨버터를 jackson 라이브러리를 사용합니다.

jackson 을 통해 컨트롤러에서 전달받은 json 데이터를 DTO로 변환해주거나,

컨트롤러로 데이터를 넘길 때 DTO를 json 으로 변환하는 역할을 해줍니다.


Spring Code 따라가보기.

아래 컨트롤러를 예시로 한 번 따라가 보도록 하겠습니다.

@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
   return data;
}

 

 

dispatcherServlet 어댑터 찾기
dispatcherServlet 어댑터 실행

getHandlerAdapter() : 핸들러 어댑터를 찾습니다.

handle() : 핸들러 어댑터의 handle() 메소드를 실행하여 어떤 핸들러든 ModelAndView 를 반환받습니다.

* 여기서 어떤 핸들러든 동일한 반환 값 (ModelAndView)을 받기 위해 핸들러 어댑터가 사용됩니다.

 

AbstractHandlerMethodAdapter
RequestMappingHandlerAdapter

RequestMapping 으로 컨트롤러가 빈에 등록되어 있기 때문에, "RequestMappingHandlerAdapter" 를 타게 됩니다.

 

동기 / 비동기에 따라 각각 invokeHandlerMethod() 메소드를 실행합니다.

 

invokeHandlerMethod

invokeAndHandle() : 핸들러(컨트롤러)를 실행합니다.

getModelAndView() : 컨트롤러 결과에 따라 ModelAndView 를 만들어서 반환합니다.

 

invokeAndHandle

invokeForRequest() : 핸들러(컨트롤러)를 실행하고, 리턴 값을 받습니다.

 

invokeForRequest

getMethodArgumentValues() : 호출할 핸들러(컨트롤러)의 arguments 를 구합니다.

doInvoke() : 핸들러(컨트롤러) 를 호출합니다.

 

getMethodArgumentValues

findProvidedArgument() : exception 을 처리할 때 args를 처리하는 것 같습니다. (이건 좀 더 알아봐야될 것 같습니다.)

this.resolvers.resolveArgument() : 일치하는 HandlerMethodArgumentResolver 를 찾아 실행하여 컨트롤러에 필요한 argumnet를 셋팅합니다.

 

이렇게 한 번 Spring 코드를 따라가보면서 컨트롤러가 어떻게 실행되는 지 알아보았습니다.

 


☺️ 결론

뒤죽박죽 이것저것 탐방한 것들을 쭈욱 적긴 했지만,

결국 처음 본론에서 얘기한 MVC 처리 정리 가 간단한 MVC 패턴 구조 설명으로 봐주시면 될 것 같습니다.

 

Spring 은 정말 추상화가 정말.. 잘 되어 있기 때문에 막상 코드를 하나하나 따라가려고 하다보면

이해가 됐던 것도 따라가다보면 길을 헤매는 경험을 하게 되었습니다..

 

역시 추상화는 사용자를 편하게 하지만, 개발할 때나 디버깅 할 때는 쫒아가기 어려운 것 같습니다..

 

잘못된 내용 혹은 궁금한 내용을 댓글 부탁드립니다! (__)

 

이 내용은 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 인프런 강의를 학습한 후 정리한 내용입니다.

 

참고

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
728x90
반응형