본문 바로가기

스프링 부트

📋 Spring MVC - 5. 스프링 MVC

2021-05-03글

스프링 MVC 전체 구조

지금껏 만들었던 프레임 워크 <-> 스프링 MVC

  • FrontController - DispatcherServlet
  • HandlerMappingMap - HandlerMapping
  • MyHandlerAdapter - HandlerAdapter
  • ModelView - ModelAndView
  • ViewResolver - ViewResolver
  • MyView - View

DispatcherServlet

DispacherServlet 도 부모 클래스에서 HttpServlet 을 상속 받아서 사용하고, 서블릿으로 동작한다.

스프링 부트는 DispatcherServlet를 자동으로 등록하면서 모든 경로에 대해서 매핑한다.

요청의 흐름

DispatcherServlet이 호출되면 service() 가 호출된다.
최종적으로는 DispatcherServlet.doDispatch()` 가 호출된다.

doDispatch()

DispatcherServlet의 doDispatch()의 주요 부분들을 살펴보자!

  • ModelAndView
  • getHandler() : 핸들러를 가져온다.
    • 핸들러가 없으면 404로 셋팅을 한다.
  • getHandlerAdapter() : 핸들러 어댑터를 가져온다.

  • 핸들러 어댑터로 핸들러 호출하고 ModelAndView 반환

  • view 렌더링 호출

  • ViewResolver를 통해서 view를 찾아 반환

  • View 렌더링

스프링 MVC 구조

  1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러 조회
  2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터 조회
  3. 핸들러 어댑터 실행 : 핸들러 어댑터 실행
  4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러 실행
  5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
  6. viewResolver 호출 : JSP의 경우 InternalResourceViewResolver 가 자동으로 등록되고 사용됨
  7. view 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고 렌더링 역할을 담당하는 뷰 객체 반환
    • JSP의 경우 InternalResourceViewResolver(JstlView) 를 반환하고 내부에 forward() 로직이 있다.
  8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링 한다.

가장 큰 장점은 DispatcherServlet 코드의 변경 없이 원하는 기능을 변경하거나 확장할 수 있다.
대부분 확장 가능하도록 인터페이스로 제공한다.

하지만 사실 우리가 확장할 컨트롤러는 거의 없다.


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

과거 Controller 인터페이스

public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
  • @Component : ""/springmvc/old-controller: 라는 이름의 스프링 빈으로 등록
  • 빈의 이름으로URL을 매핑

위 컨트롤러가 호출되는 방식

  • HandlerMapping(핸들러 매핑)
    • 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 함
      ex) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑 필요
  • HandlerAdapter(핸들러 어댑터)
    • 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요
      ex) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 함
  • 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다.

스프링 부트가 자동으로 등록하는 핸들러 매핑과 핸들러 어댑터

  • HandlerMapping
    • RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용 - 가장 우선순위가 높음
    • BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
      (위 예제의 @Component)
  • HandlerAdapter
    • RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용 - 가장 우선순위가 높음
    • HttpRequestHandlerAdapter : HttpRequestHandler 처리
    • SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
1. 핸들러 매핑으로 핸들러 조회

HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.

빈이름으로 핸들러를 찾아야하기 때문에 빈이름으로핸들러를찾아주는 BeanNameUrlHandlerMapping가 실행에 성공하고 핸들러인 OldController 를 반환한다.

2. 핸들러 어댑터 조회

HandlerAdapter 의 supports() 를 순서대로 호출한다. SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.

3. 핸들러 어댑터 실행

DispatcherServlet이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.

SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.

HttpRequestHandler

 public interface HttpRequestHandler {
         void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
  • 서블릿과 가장 유사한 형태의 핸들러
1. 핸들러 매핑으로 핸들러 조회

HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.

빈이름으로 핸들러를 찾아야하기 때문에 빈이름으로 핸들러를찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 MyHttpRequestHandler 를 반환한다.

2. 핸들러 어댑터 조회

HandlerAdapter 의 supports() 를 순서대로 호출한다.
HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.

3. 핸들러 어댑터 실행

DispatcherServlet이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.

HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.


뷰 리졸버

application-properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

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

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

  • BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환
  • InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환

1. 핸들러 어댑터 호출
핸들러 어댑터를 통해 논리 뷰 이름을 획득

2. ViewResolver 호출

new-form 이라는 뷰 이름으로 viewResolver를 순서대로 호출
InternalResourceViewResolver 가 호출됨

3. InternalResourceViewResolver

내부에서 자원을 찾을 수 있음을 의미한다.
이 뷰 리졸버는 InternalResourceView 를 반환

4. - InternalResourceView

InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용

5. view.render()

view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다

Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야 한다.
최근에는 라이브러리만 추가하면 스프링 부트가 이런 작업도 모두 자동화해준다.