Carefree Hub

Command Palette

Search for a command to run...

Spring MVC 기본 구조

Spring MVC 기본 구조

Spring MVC의 DispatcherServlet, 핸들러 매핑, 어댑터, 뷰 리졸버의 동작 원리

Carefreelife98
4분 소요

Spring MVC의 기본 구조

Spring MVC 동작 순서

  1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(Controller) 를 조회한다.
  2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
  3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행
  4. 핸들러 실행: 핸들러 어댑터로 인해 실제 핸들러가 실행된다.
  5. ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView 로 변환하여 반환.
  6. viewResolver 호출: 뷰 리졸버를 찾아 실행한다.
  7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 변환 후 Rendering 역할을 담당하는 뷰 객체를 반환한다.
  8. 뷰 렌더링: 뷰를 통해 뷰를 렌더링 한다.

Spring MVC도 프론트 컨트롤러 패턴으로 구현되어 있다. Spring MVC에서 프론트 컨트롤러 역할은 DispatcherServlet이 맡고 있으며, 이는 곧 Spring MVC의 핵심이다.

DispatcherServlet

DispatcherServlet의 서블릿 등록

  • DispatcherServlet의 상위 클래스를 따라가다 보면 HttpServlet을 상속받아 서블릿으로 동작하고 있는 것을 알 수 있다.
  • 스프링 부트가 내장 WAS (내장 톰캣 서버 등) 를 띄우는 동시에 DispatcherServlet을 서블릿으로 자동 등록한다.
  • 그 과정에서 모든 경로('urlpatterns="/"')에 대하여 매핑해준다.
    • 하지만 위와 같이 모든 경로에 대하여 매핑이 되면 해당 작업의 우선 순위가 가장 낮아져 더욱 자세한 하위 경로에 대한 매핑도 잘 동작한다.

doDispatch

  • 서블릿이 호출된 후 HttpServlet이 제공하는 Service()가 호출된다.
  • 최종적으로 제일 중요한 DispatcherServlet.doDispatch()가 호출된다.
java
1protected void doDispatch(HttpServletRequest request, HttpServletResponse response){
2 ...
3 ModelAndView mv = null; // modelAndView 사용
4 ...
5 // 1. 핸들러 조회
6 mappedHandler = getHandler(processedRequest);
7 if (mappedHandler == null) {
8 noHandlerFound(processedRequest, response); // 핸들러가 없으면 404 오류
9 return;
10 }
11 // 2. 핸들러 어댑터 조회
12 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
13 ...
14
15 // 3. 핸들러 어댑터 실행 -> 4. 핸들러 실행 -> 5. ModelAndView 반환
16 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
17 ...
18
19 // render()를 반환하는 processDispatchResult 가 실행된다.
20 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException){
21 ...
22 // 뷰 렌더링 호출
23 render(mv, request, response) {
24 View view;
25 String viewName = mv.getViewName();
26 ...
27 // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
28 view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
29
30 // 8. 뷰 렌더링
31 view.render(mv.getModelInternal(), request, response);
32 }
33 }
34}

주요 인터페이스

  • 핸들러 매핑: org.springframework.web.servlet.HandlerMapping
  • 핸들러 어댑터: org.springframework.web.servlet.HandlerAdapter
  • 뷰 리졸버: org.springframework.web.servlet.ViewResolver
  • 뷰: org.springframework.web.servlet.View

핸들러 매핑과 핸들러 어댑터

컨트롤러를 호출 하는것에 있어서 2가지의 전제 조건이 성립해야 한다.

  1. HandlerMapping(핸들러 매핑): 핸들러 매핑에서 해당 컨트롤러를 찾을 수 있어야 한다.
  2. HandlerAdapter(핸들러 어댑터): 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.

Spring Boot 가 자동 등록하는 핸들러 매핑과 핸들러 어댑터

java
1// HandlerMapping
20 = RequestMappingHandlerMapping // @RequestMapping 에서 사용 (대부분 이 방법을 사용)
31 = BeanNameUrlHandlerMapping // 스프링 빈의 이름으로 핸들러를 찾는다.
java
1// HandlerAdapter
20 = RequestMappingHandlerAdapter // @RequestMapping 에서 사용
31 = HttpRequestHandlerAdapter // HttpRequestHandler 처리
42 = SimpleControllerHandlerAdapter // Controller 인터페이스 처리

핸들러 매핑과 핸들러 어댑터를 위 순서대로 찾아 실행하며, 만약 없다면 다음 순서로 넘어가 찾는다.

핸들러 매핑 및 핸들러 어댑터의 조회 및 실행 과정

  1. 핸들러 매핑을 통한 핸들러 조회: HandlerMapping 을 위 순서대로 실행하여 핸들러를 찾는다.
  2. 핸들러 어댑터 조회: HandlerAdapter 의 supports()를 순서대로 호출한다.
  3. 핸들러 어댑터 실행: DispatcherServlet 이 조회한 핸들러 어댑터를 실행하면서 핸들러 정보도 함께 넘겨준다.

@RequestMapping

가장 우선 순위가 높은 핸들러 매핑과 어댑터는 RequestMappingHandlerMappingRequestMappingHandlerAdapter 이다. 실무에서는 99.9% 이 방식의 컨트롤러를 사용한다.

뷰 리졸버 (InternalResourceViewResolver)

  • 스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록해준다.
  • application.properties 에 등록한 설정 정보를 사용해서 등록한다.
properties
1spring.mvc.view.prefix=/WEB-INF/views/
2spring.mvc.view.suffix=.jsp

뷰 리졸버의 동작 방식

스프링 부트가 자동 등록하는 뷰 리졸버:

java
11 = BeanNameViewResolver // 빈 이름으로 뷰를 찾아서 반환한다.
22 = InternalResourceViewResolver // JSP를 처리 할 수 있는 뷰를 반환한다.

동작 과정

  1. 핸들러 어댑터 호출: ModelAndView("new-form") 을 통해 논리 뷰 이름을 획득
  2. ViewResolver 호출: 뷰 이름으로 viewResolver 를 순서대로 호출
  3. InternalResourceViewResolver: InternalResourceView 를 반환
  4. 뷰 - InternalResourceView: forward() 를 호출해서 처리
  5. view.render(): JSP를 실행

참고: Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver를 등록해야 하며, 라이브러리만 추가하면 스프링 부트가 자동으로 등록해준다.


참고: Inflearn - 김영한님 강의