Spring MVC 구조
DispatcherServlet (Front Controller)
이름에 Servlet이 있다시피 DispatcherServlet은 Servlet이고, HttpServlet을 상속 받는다.
DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
스프링 부트는 DispatcherServlet을 Servlet으로 등록할때 모든 경로 (urlPattern = "/") 으로 매핑해서 등록한다.
DispatcherServlet 은 Spring MVC에서 Front Controller 역할을 한다.
Front Controller Pattern 이란 클라이언트에서 요청온 요청들을 처리하는 컨트롤러들의 공통적인 부분들을 모아서 Front Controller에서 처리하고, Front Controller가 각 요청에 맞는 컨트롤러를 찾아서 실행시키도록 하는 것이다.
Spring MVC 에서 DispatcherServlet이 Front Controller를 담당한다.
클라이언트에서 서버로 요청이 오면 여러 작업들이 수행될 것인데, 이런 여러 작업들을 담당하는 객체, 인터페이스, 혹은 함수들이 따로 있다.
DispatcherServlet은 Front Controller로서 클라이언트에서 요청이 들어오면 해당 역할들을 담당하는 객체들을 순서대로 호출하고, 각 역할을 담당하는 객체들은 결과를 다시 DispatcherServlet에 리턴한다.
즉 DispatcherServlet은 Front Controller 로서 클라이언트에서 들어온 요청을 각 객체에게 명령하고 그 결과를 모아서 요청을 처리한다.
Handler(Controller) Mapping
클라이언트에서 요청이 DispatcherServlet 으로 오면, DispatcherServlet은 해당 요청을 처리할 컨트롤러를 찾아야 한다.
이때 Handler Mapping 을 이용해 해당 요청 URL 을 처리할 컨트롤러를 찾는다.
스프링 부트는 자동으로 HandlerMapping 을 만들어 놓기 때문에 대부분의 경우 따로 우리가 만들 필요가 없다.
예를들어 @RequestMapping 애노테이션이 으로 매핑 하는 RequestMappingHandlerMapping, 스프링 빈의 이름으로 핸들러를 찾는 BeanNameUrlHandlerMapping 을 자동등록 한다.
이런 handler mapping 들을 이용해 DispatcherServlet은 사용할 핸들러(컨트롤러)를 찾는다.
ex)
BeanNameUrlHandlerMapping - 스프링 빈의 이름으로 핸들러 찾음
RequestMappingHandlerMapping - 에노테이션 기반인 컨트롤러의 @RequestMapping 으로 찾음
즉 HandlerMapping 은 어떻게 컨트롤러를 찾을 것인가를 정의하고 찾음.
최종적으로 컨트롤러(핸들러) 리턴함
Handler Adapter
사용할 핸들러를 찾았다면 Handler Adapter가 실제 핸들러를 호출한다.
Handler Adapter에는 다음과 같은 supports 메서드가 오버라이딩 되어 있고, 이 함수를 호출해 핸들러와 핸들러 어댑터가 호환되는지 검사한다.
boolean supports(Object handler);
Handler Adapter 는 실제 Handler 를 실행시킨다.
실행된 컨트롤러는 나름의 로직을 처리한 후 뷰이름을 반환할 것인데, 이 반한된 뷰 이름을 ModelAndView 로 변환해 DispatcherServlet 에게 반환하는 것도 Handler Adapter가 하는 일이다.
여기까지 생각하면 Handler Adapter 의 역할에 대해 조금 의문이 생긴다.
핸들러 어댑터가 실제 핸들러를 호출하고, 뷰 이름을 ModelAndView 로 변환시켜 주는 것은 알겠는데 이 일을 꼭 Handler Adapter가 해야하는지 의문이 들고 따라서 Handler Adapter가 왜 필요한지 의문이 생긴다.
Spring docs의 HandlerAdapter를 보면 설명이 있다.
HandlerAdapter (Spring Framework 5.3.23 API)
MVC framework SPI, allowing parameterization of the core MVC workflow. Interface that must be implemented for each handler type to handle a request. This interface is used to allow the DispatcherServlet to be indefinitely extensible. The DispatcherServlet
docs.spring.io
Handler Adapter 인터페이스는 열어보면 단 3개의 메서드만 존재한다.
supports, handle, getLastModified.
이중에서 supports는 핸들러와 핸들러 어댑터가 호환 되는지 체크한다.
handle 은 핸들러를 호출하고 뷰를 ModelAndView 로 리턴한다.
이렇게 HandlerAdapter는 별거 없는 아주 작은 인터페이스 인데 존재하는 이유는 설명에 다음과 같이 나와있다
Interface that must be implemented for each handler type to handle a request. This interface is used to allow the DispatcherServlet to be indefinitely extensible. The DispatcherServlet accesses all installed handlers through this interface, meaning that it does not contain code specific to any handler type.
DispatcherServlet은 실제 핸들러를 직접 호출 하는 것이 아니라 Handler Adapter를 호출하기 때문에 DispatcherServlet은 모든 Handler에 대하여 알필요가 없고 HandlerAdapter에 대한 것만 알고 있으면 된다.
즉 이 인터페이스는 확장성을 위한 인터페이스 인 것이다.
이렇게 함으로서 새로운 핸들러가 추가되도 DispatcherServlet에 직접적인 변경은 불필요해진다.
ex)
RequestMappingHandlerAdapter - 에노테이션 기반인 컨트롤러의 @RequestMapping 에서 사용
HttpRequestHandlerAdapter - HttpRequstHandler 처리
SimpleControllerHandlerAdapter - Controller 인터페이스 처리
Handler (Controller)
핸들러는 핸들러 어댑터에 의해 호출되고 실제 요청에 필요한 로직들을 처리하고 response에 필요한 데이터들을 모델에 담기도 할 것이다.
그 후 사용할 뷰 이름을 핸들러 어댑터에게 리턴한다.
View Resolver
핸들러 어댑터는 ModelAndView를 반환하고 이를 통해 논리 뷰 이름을 획득한다 (실제 물리 뷰 이름이 아닌 prefix, surfix 제외한 뷰 이름) .
이제 이 뷰이름으로 viewResolver 들을 순서대로 호출해서 Handler Adapter의 supports 메서드처럼 맞는 뷰 리솔버를 찾는다.
스프링 부트는 RequestMappingHandlerMapping 이나 BeanNameUrlHandlerMapping 를 자동 등록한 것 처럼 여러 뷰 리솔버들도 자동으로 등록해 놓는다.
(BeanNameViewResolver=빈 이름으로 뷰 찾아 반환, InternalResourceViewResolver=jsp 처리하는 뷰 이름 반환 등)
랜더링 할때 뷰 템플릿 엔진인 thymeleaf를 많이 쓰는데, 사실 thymeleaf를 사용하려면 ThymeleafViewResolver를 등록해야 한다. 그런데 스프링 부트를 쓸때 start.spring.io 에서 그냥 thymeleaf를 등록하면 이런 작업도 모두 대신 해준다.