본문 바로가기
Spring

Spring - MVC 작동 원리

by 오늘부터개발시작 2022. 12. 13.

 

 

3 ~ 4년간 스프링을 잘 사용해왔지만 내부적으로 어떻게 작동하는지에 대해서는 대충 겉핥기로 알고 있었다. 항상 어떻게 동작하는지 궁금증이 있었지만 정보를 찾기 어려웠다. 우연치 않게 김영한님의 스프링 MVC 강의를 듣게 됐고 명쾌한 답을 찾을 수 있었다. 강의에서 공부한 내용을 바탕으로 스프링 MVC가 어떻게 내부적으로 동작하는지에 대해서 최대한 상세히 정리해보려고 한다.

 

스프링 MVC 구조

ServletContainer 역할 

순차적으로 어떻게 스프링 MVC가 동작하는지 설명해보겠다. 시나리오는 @RequestMapping을 사용했다고 가정한다.

 

사용자가 스프링 서버에 HTTP 요청을 날리면 WAS가 요청을 받는다. 이때 WAS는 스레드를 하나 생성하고 HttpServletResponse와 HttpServletRequest를 만들어서 DispatcherServlet에 전달한다. DispatcherServlet은 크게 다음과 같은 순서로 동작한다.

 

1. HandlerMapping 조회

 

HandlerMapping을 조회하는 것은 사용자가 요청한 url을 가지고 우리가 @Controller 혹은 @RestController에 정의한 메소드를 찾는 것이다. Method가 Handler라고 생각하면 된다. 사진을 보면 HandlerMapping에 다양한 종류가 있는 것을 확인할 수 있는데, 우리는 @RequestMapping을 사용해서 컨트롤러를 정의했기 때문에 RequestMappingHandlerMapping을 통해 Handler를 찾을 수 있다.

최초에 스프링이 실행되면 @RequestMapping이 붙어있는 모든 메소드들이 RequestMappingHandlerMapping에 등록이 된다.

 

+ 만약 우리가 @RequestMapping을 사용하지 않고 BeanNameUrl을 사용해 컨트롤러를 정의했다면 BeanNamUrlHandlerMapping을 사용해 Handler를 찾을 수 있을 것이다. 원하는 방법에 따라 다양하게 컨트롤러를 개발할 수 있는 구조인 것이다. 

 

 

2. HandlerAdapter 조회

스프링을 사용해서 API를 개발해봤다면 알겠지만 API A는 ResponseEntity를 리턴하고, API B는 String을 리턴하고, API C는 아무것도 리턴하지 않는다. 만약 Handler마다 모두 스펙이 다르고 중구난방으로 값을 리턴하게 되면 공통된 로직을 사용하는 DispatcherServlet은 대응이 불가능할 것이다. 

 

이를 해결하기 위해 존재하는 것이 HandlerAdapter이다. HandlerAdapter가 Handler의 로직을 내부적으로 수행하고 Handler가 어떤 값을 반환하던지 가공을 통해 반드시 "ModelAndView"를 DispatcherServlet에게 반환해준다.

 

첫번째 사진에서 볼 수 있듯이 HandlerAdapter도 여러가지가 있고 HandlerMapping과 동일하게 우리는 RequestMappingHandlerAdapter를 사용한다. 

 

3. HandlerAdapter에 Handler 전달

+ Handler가 전달되기 전, 이 시점에 미리 등록해둔 Interceptor들이 있다면 적용된다.

4. Argument Resolver 

컨트롤러에 속한 메소드의 예시이다. 위의 예시처럼 메소드에 파라미터로 DTO를 넣든, HttpServletRequest를 넣든, Pageable을 넣든 스프링이 알아서 주입해줘서 우리는 편리하게 사용할 수 있다. 이렇게 값을 주입해주는 역할을 하는 것이 바로 ArgumentResolver이다.

 

ArgumentResolver는 인터페이스로 RequestParamMethodArugmentResolver, XXX-ArgumentResolver 형식의 수많은 구현체가 스프링에 미리 등록되어 있어서 우리의 필요에 따라 값을 주입해준다. ArgumentResolver는 HandlerAdapter 안에 존재하면서 Handler의 파라미터를 보고 필요한 값을 주입해준다. 

 

5. 생성된 Argument 전달

Controller 메소드에 @RequestBody가 있을 경우에는 HttpMessageConverter를 사용하여 값을 주입해주기도 한다.

 

+ 내부적으로 Converter와 Fomatter를 사용하기도 한다. 메소드에 Integer를 파라미터로 받을 수 있는 이유가 바로 Converter가 String을 Integer로 자동으로 변환시켜주기 때문이다. 

 

6. 비지니스 로직 처리 후 리턴

Handler(Controller)는 비지니스 로직을 실행한 후에 값을 리턴한다.

 

7. ReturnValueHandler 

HandlerAdapter 속에 존재하며 컨트롤러에서 리턴 받은 값을 처리해 HandlerAdapter가 어떻게든 ModelAndView를 반환하게 할 수 있도록 돕는다. @ResponseBody를 리턴할 경우에는 HttpMessageConverter를 사용하여 HTTP 응답 Body를 채워주고, 간단한 View의 이름이 반환될 때는 ViewNameMethodReturnValueHandler가 사용되며, 단순히 ModelAndView에 값을 채워넣어준다. 

 

8. ModelAndView를 반환

이번 단계에서는 2가지의 경우로 나눠진다.

View를 반환하는 경우 -  ModelAndView에 값을 채워서 반환한다.

@ResponseBody의 경우 -  ModelAndView를 null 값으로 반환한다. 

추후 로직에서는 아래 사진처럼 ModelAndView의 null 유무를 통해 view를 rendering 할 것 인지, 응답을 바로 반환할 것인지 정한다.

+ ModelAndView를 반환하고나서 만약 Interceptor에 postHandle이 정의되어 있다면 호출된다.

 

9. ViewResolver 호출

만약 ModelAndView가 null이 아니면 알맞은 ViewResolver를 찾아서 호출한다. ViewResolver도 첫번째 사진에서 확인할 수 있듯이 다양한 구현체가 존재한다. 템플릿 엔진이 JSP, Thymleaf, Groovy, 등 다양하게 존재하기 때문이다. ViewResolver는 사용중인 템플릿 엔진에 따라 View를 반환한다.

 

10. View 반환

ViewResolver에서 DispatcherServlet으로 View를 반환한다.

 

11. 응답

ServletContainer로 처리된 값이 반환되고 ServletContainer가 사용자에게 응답을 반환한다.