4 minute read


springmvcstruct_Procdess
Spring MVC의 기본 구조
참고:Inflearn - 김영한님_강의

Spring MVC 동작 순서

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



Spring MVC의 기본 구조에 대해 알아보자.

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



DispatcherServlet

Dispatcher Servlet?

(1) DispatcherServlet의 서블릿 등록

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



(2) doDispatch

  • 서블릿이 호출된 후 HttpServlet이 제공하는 Service()가 호출된다. (DispatcherServlet이 상속하고 있는 FrameworkServlet에서 service()를 override 해둠.)
  • 최종적으로 제일 중요한 DispatcherServlet.doDispatch()가 호출된다.
  • protected void doDispatch(HttpServletRequest request, HttpServletResponse response){
      ...
      ModelAndView mv = null; // modelAndView 사용
      ...
      // 1. 핸들러 조회
      mappedHandler = getHandler(processedRequest);   // 핸들러를 찾는다.
                  if (mappedHandler == null) {
                      noHandlerFound(processedRequest, response); // 핸들러가 없으면 SC_NOT_FOUND : 404 오류를 발생시킨다.
                      return;
                  }
      // 2. 핸들러 어댑터 조회 - getHandler()하여 찾은 핸들러를 처리할 수 있는 핸들러 어댑터 조회
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  // getHandler()하여 찾은 핸들러를 가지고 핸들러 어댑터를 찾는다.
      ...
      
      // 3. 핸들러 어댑터 실행 (ha) -> 4. 핸들러 어댑터를 통해 핸들러 실행 (ha.handle()) -> 5. ModelAndView 반환 (mv)
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 찾은 실제 핸들러 어댑터를 ModelAndView 로 받는다.
      ...
        
      // render()를 반환하는 processDispatchResult 가 실행된다.
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException){
          ...
          // 뷰 렌더링 호출
          render(mv, request, response) {
              View view;
              String viewName = mv.getViewName();
              ...
              // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
              view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      
              // 8. 뷰 렌더링 (JSP Forward logic이 포함되어 있다)
              view.render(mv.getModelInternal(), request, response);          
          }
      }
    }
    



주요 인터페이스

  • 핸들러 매핑: org.springframework.web.servlet.HandlerMapping
  • 핸들러 어댑터: org.springframework.web.servlet.HandlerAdapter
  • 뷰 리졸버: org.springframework.web.servlet.ViewResolver
    • Spring 용, Thymeleaf 용 뷰 리졸버가 각각 따로 제공된다.
  • 뷰: org.springframework.web.servlet.View
    • JSP용 뷰, Thymeleaf용 뷰가 따로 존재한다.



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

컨트롤러의 호출 방법 및 과정

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

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



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

// HandlerMapping
0 = RequestMappingHandlerMapping // 어노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용 (대부분 이 방법을 사용)
1 = BeanNameUrlHandlerMapping // 스프링 빈의 이름으로 핸들러를 찾는다.
// HandlerAdapter
0 = RequestMappingHandlerAdapter // 애노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용.
1 = HttpRequestHandlerAdapter // HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter // Controller 인터페이스 처리 (Annotation 이 아닌, 과거에 사용하던 Controller)
  • 핸들러 매핑과 핸들러 어댑터를 위 순서대로 찾아 실행하며, 만약 없다면 다음 순서로 넘어가 찾는다.



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

  1. 핸들러 매핑을 통한 핸들러 조회
    • HandlerMapping 을 위 순서대로 실행하여 핸들러를 찾는다.
  2. 핸들러 어댑터 조회
    • HandlerAdapter 의 supports()를 순서대로 호출한다.
  3. 핸들러 어댑터 실행
    • DispatcherServlet 이 조회한 핸들러 어댑터를 실행하면서 핸들러 정보도 함께 넘겨준다.
    • 해당 핸들러 어댑터는 넘겨받은 핸들러(정보)를 내부에서 실행하고, 그 결과(ModelAndView(“논리 뷰 이름 == new-form”))를 반환.

@RequestMapping

가장 우선 순위가 높은 핸들러 매핑과 어댑터는

RequestMappingHandlerMapping (핸들러 매핑)

RequestMappingHandlerAdapter (핸들러 어댑터)

@RequestMapping 의 앞글자를 따서 만든 이름이며,
위 두가지가 바로 현재 스프링에서 주로 사용하는 Annotation 기반의 컨트롤러를 지원하는 매핑과 어댑터이다.
실무에서는 99.9% 이 방식의 컨트롤러를 사용한다고 한다.



뷰 리졸버 (InternalResourceViewResolver)

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

    spring.mvc.view.prefix=/WEB-INF/views/  // prefix 이후는 예시
    spring.mvc.view.suffix=.jsp // suffix 이후는 예시
    



뷰 리졸버의 동작 방식

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

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



동작 과정

  1. 핸들러 어댑터 호출
    • 핸들러 -> 핸들러 어댑터가 반환하는 ModelAndView("new-form") 을 통해 new-form(예시) 이라는 논리 뷰 이름을 획득
  2. ViewResolver 호출
    • new-form 이라는 뷰 이름으로 viewResolver 를 순서대로 호출한다.
    • BeanNameViewResolvernew-form이라는 이름의 스프링 빈으로 등록된 뷰를 찾는다.
    • 만약 해당 사항이 없다면 다음 호출 순서인 InternalResourceViewResolver가 호출된다.
  3. InternalResourceViewResolver
    • InternalResourceView 를 반환한다.
  4. 뷰 - InternalResourceView
    • InternalResourceView는 JSP 처럼 forward() 를 호출해서 처리할 수 있는 경우에 사용.
  5. view.render()
    • view.render() 가 호출되어 InternalResourceViewforward() 를 사용하여 JSP를 실행.



참고

  • 여타 뷰는 실제 뷰를 렌더링. JSP의 경우 forward()를 통해서 해당 JSP로 이동(실행)해야만 렌더링 가능.
    • JSP를 제외한 나머지 View(Thymeleaf … etc)템플릿들은 forward() 과정없이 바로 렌더링이 된다.

  • Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver를 등록해야 사용이 가능하다.
    • 이는 라이브러리만 추가하면 스프링 부트가 모두 자동화 해준다.



최대한의 설명을 코드 블럭 내의 주석으로 달아 놓았습니다.

혹시 이해가 안가거나 추가적인 설명이 필요한 부분, 오류 등의 피드백은 언제든지 환영합니다!

긴 글 읽어주셔서 감사합니다. 포스팅을 마칩니다.



처음으로~



참고:Inflearn - 김영한님_강의

Task Lists

  • Spring MVC 동작 순서
  • DispatcherServlet
  • 주요 인터페이스
  • 핸들러 매핑과 핸들러 어댑터
  • Spring Boot 가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
  • 핸들러 매핑 및 핸들러 어댑터의 조회 및 실행 과정
  • 뷰 리졸버 (InternalResourceViewResolver)
  • 뷰 리졸버의 동작 방식

Comments