Spring MVC

웅평·2024년 5월 26일
0

MVC란? (MVC: Model, View, Controller)

  • MVC 패턴은 애플리케이션을 개발할 때 사용하는 디자인 패턴이다.
  • 애플리케이션의 개발 영역을 MVC(Model, View, Controller)로 구분하여 각 역할에 맞게 코드를 작성하는 개발 방식이다.
  • MVC 패턴을 도입하면서 UI 영역과 도메인(비즈니스 로직) 영역으로 구분되어 서로에게 영향을 주지 않으면서 개발과 유지보수를 가능하게 되었다.

MVC 패턴을 사용하는 이유

사용자가 보는 페이지, 데이터 처리, 그리고 이 2가지를 중간에서 제어하는 컨트롤 이렇게 3가지로 구성되는 하나의 애플리케이션을 만들면 각각 맡은 역할에만 집중을 할 수 있게 된다.

MVC 패턴의 사용 목적: 각 컴포넌트가 서로 분리되어 각자의 역할에 집중할 수 있기 때문에 시스템 결합도를 낮출 수 있다. 또한, 유지보수가 쉬우며, 중복코드를 제거할 수 있고, 애플리케이션의 확장성 및 유연성이 증가한다.

Model, View, Controller

Model(모델)
Spring MVC 기반의 웹 애플리케이션이 클라이언트의 요청을 전달받으면 요청 사항을 처리하기 위한 작업을 한다.
처리한 작업의 결과 데이터를 클라이언트에게 응답을 돌려주어야 하는데, 이때 클라이언트에게 응답으로 돌려주는 작업의 처리 결과 데이터를 Model이라 한다.

클라이언트의 요청 사항을 구체적으로 처리하는 영역을 서비스 계층(Service layer)라고 하며,
요청 사항을 처리하기 위해 Java 코드로 구현한 것을 비즈니스 로직(Business Logic)이라 한다.

View(뷰)
View는 Model을 이용하여 웹 브라우저와 같은 애플리케이션의 화면에 보이는 리소스(Resource)를 제공하는 역할을 한다.

Spring MVC에는 다양한 View 기술이 포함되어 있다.

  • HTML 페이지 출력
  • PDF, Excel 등의 문서 형태로 출력
  • XML, JSON 등 특정 형식의 포맷으로 변환

Controller(컨트롤러)
컨트롤러는 클라이언트 측의 요청을 직접적으로 전달받는 엔드포인트(Endpoint)로써 Model과 View의 중간에서 상호작용을 해주는 역할을 한다.

클라이언트 측의 요청을 전달받아 비즈니스 로직을 거친 후, Model 데이터가 만들어지면, 이 Model 데이터를 View로 전달하는 역할을 한다.

MVC1

우리가 흔히 사용하고 있는 MVC 패턴은 사실 MVC1, MVC2 아키텍쳐에서 발전된 패턴이다.

MVC1 패턴이란, 브라우저(사용자)로부터 요청이 들어오면 DB로부터 필요한 데이터를 받은 Model 객체(Java Bean)를 JSP 페이지(View)에 담아 응답으로 보내는 패턴이다.
MVC1에서는 JSP가 View와 Controller 역할을 모두 담당하기 때문에 JSP 페이지 내에 너무 많은 코드가 들어가게 된다. 따라서, 코드의 가독성이 떨어질 뿐만 아니라 코드가 복잡해질 가능성이 있다.
이러한 점을 보완하여 Controller 역할을 하는 Servlet이 추가된 MVC2 패턴이 등장하게 되었다.

MVC2

MVC2 패턴은 요청을 하나의 컨트롤러(Servlet)가 먼저 받는다.
서블릿은 요청에 대한 비즈니스 로직을 처리한 후, 이를 JSP 파일에 반영하는 역할을 수행한다.

MVC2는 MVC1 패턴보다 구조가 복잡해질 수 있으나, 이러한 문제점들을 해결하기 위해 각종 프레임워크들이 지금까지 잘 발전되어 왔고, 그 중에서 대표적인 것이 바로 스프링 프레임워크이다.

Spring MVC

웹 계층에 서블릿(Servlet) API를 기반으로 클라이언트의 요청을 처리하는 모듈이 있는데 이를 스프링 웹 MVC(spring-web-mvc) 또는 스프링 MVC라고 합니다.

  • Model은 데이터 관리 및 비즈니스 로직을 처리하는 부분 (DAO, DTO, Service 등)
  • View는 비즈니스 로직의 처리 결과를 통해 유저 인터페이스가 표현되는 구간 (html, jsp, tymeleaf, mustache 등 화면을 구성하기도 하고, Rest API로 서버가 구현된다면 json 응답으로 구성되기도 한다.)
  • Controller는 사용자의 요청을 처리하고 Model과 View를 중개하는 역할을 한다. Model과 View는 서로 연결되어 있지 않기 때문에 Controller가 사이에서 통신 매체가 되어준다.

Spring MVC의 동작 방식과 구성 요소

MVC 패턴에서도 여러 가지 구조를 가진다.

MVC 프레임워크 구조

여러 가지 구조를 가진 MVC 패턴 중 위 그림의 구조를 그대로 사용하고 있는 것이 Spring MVC이다.
일반 MVC 패턴도 유지보수하기에 좋은 패턴임은 확실하나, 구조가 복잡해지는 한계가 있다.
이러한 한계를 극복하게 해주는 것이 Spring MVC이다.

Spring MVC 구조

우리가 FrontController라 부르던 서블릿은 DispatcherServlet으로 쓰이고 있고, handlerMappingMap은 HandlerMapping으로 불리는등 이름만 달라졌고, 사용처는 똑같다. 이름이 달라진 부분은 다음과 같다.

  • FrontController → DispatcherServlet
  • handlerMappingMap → HandlerMapping
  • MyHandlerAdapter → HandlerAdapter
  • ModelView → ModelAndView
  • viewResolver → ViewResolver
  • MyView → View

DispatcherServlet

DispatcherServlet은 HttpServlet을 상속받아 사용하고, 서블릿으로 동작한다.

DispatcherServlet → FrameworkServlet → HttpServletBean → HttpServlet

DispacherServlet을 사용하면 서블릿으로 등록하면서 모든 경로에 (urlPatterns=”/”)에 대해 매핑한다. 즉, 클라이언트에게 요청을 받아 응답까지의 MVC 처리과정을 통제한다.

DispatcherServlet - diagram

요청 흐름
1. 서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
2. 스프링 MVC는 FramworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch()가 최종적으로 호출된다.

DispatcherServlet.doDispatch() 핵심 로직 분석

메서드 이름대로 해당 메서드에서 적절한 컨트롤러를 찾아 매핑해주고 뷰까지 찾아 View를 반환해 렌더링까지 해주는 핵심 메서드이다.

protected void doDispatch(HttpServletRequest request, 
                              HttpServletResponse response) throws Exception {

    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    ModelAndView mv = null;

    // 1. 핸들러 조회
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
    }
    //2.핸들러 어댑터 조회-핸들러를 처리할 수 있는 어댑터
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    /**
     * 3. 핸들러 어댑터 실행 
     * -> 4. 핸들러 어댑터를 통해 핸들러 실행 
     * -> 5. ModelAndView 반환 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
     */
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(HttpServletRequest request,
                                   HttpServletResponse response, 
                                   HandlerExecutionChain mappedHandler, 
                                   ModelAndView mv, Exception exception) throws Exception {
    // 뷰 렌더링 호출
    render(mv, request, response);
}

protected void render(ModelAndView mv, HttpServletRequest request,
                      HttpServletResponse response) throws Exception {
    View view;
    String viewName = mv.getViewName(); //6. 뷰 리졸버를 통해서 뷰 찾기,7.View 반환
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    // 8. 뷰 렌더링
    view.render(mv.getModelInternal(), request, response);
}
  • mappedHandler = getHandler(processedRequest);
    ⇒ 요청에 맞는 적절한 핸들러를 찾아 반환해준다.
  • noHandlerFound(processedRequest, response);
    ⇒ 적절한 핸들러를 찾지 못한경우 404 에러코드를 반환해준다.
  • HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    ⇒ 찾은 핸들러를 처리할 수 있는 핸들러 어댑터를 찾아준다.
    ⇒ 만약 찾지 못할경우 ServletException 발생
  • mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    ⇒ 찾은 핸들러 어댑터를 이용해 로직을 수행하는 handle 메서드를 호출한다.
    ⇒ 결과로 ModelAndVIew를 반환받고, 이를 이용해 렌더링까지 수행된다.
  • processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    ⇒ 실제 코드는 복잡하게 되있는데 결국 render() 메서드를 호출해준다.
    ⇒ render() 에서는 ModelAndView에서 View를 찾아 뷰 리졸버를 이용해 뷰의 물리적 이름을 완성해서 forward 해준다.

HandlerMapping

클라이언트의 요청 URL을 어떤 Controller가 처리할지 결정한다.

HandlerAdapter

HandlerMapping에서 결정된 핸들러 정보로 해당 메소드를 직접 호출해주는 역할을 한다.

Controller

DispatcherServlet이 전달해준 HTTP 요청을 처리하고 결과를 Model에 저장한다.

  • 직접 요청을 처리하며, 처리 결과를 반환한다.
  • 결과가 반환되면 HandlerAdapter가 ModelAndView 객체로 변환되며, 여기에는 View Name과 같이 응답을 통해 보여줄 View에 대한 정보와 관련된 데이터가 포함되어 있다.

ModelAndView

ModelAndView는 Controller에 의해 반환된 Model과 View가 Wrapping된 객체이다.

ViewResolver

Controller의 처리 결과(데이터)를 생성할 view를 결정한다.

동작 순서

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

Spring MVC 패턴에 장점 & 단점

장점

  1. Model , View , Controller 로 나누어서 개발을 할 수 있기에 여러 개발자가 역할을 분담해서 개발을 진행할 수 있다.
  2. 중복된 코드를 줄일 수 있고, 확장성 있고 유연한 코딩이 가능하다.
  3. 각 컴포넌트별로 나누어져서 있기 때문에 디버깅과 테스팅이 용이하다.

단점

  1. 프로젝트의 규모가 커지면 복잡해질 수 있다. 즉 너무 많은 파일이 생성될 수 있다.
  2. 하나의 파일에서 모든 로직을 처리하는 대신 여러 파일로 나누어서 개발을 하기 때문에 유지보수 시간이 길어질 수 있다.

Spring MVC 요약

MVC는 Model, View, Controller의 약자이며, 각 레이어간 기능을 구분하는데 중점을 둔 디자인 패턴입니다.

  • Model은 데이터 관리 및 비즈니스 로직을 처리하는 부분 (DAO, DTO, Service 등)
  • View는 비즈니스 로직의 처리 결과를 통해 유저 인터페이스가 표현되는 구간 (html, jsp, tymeleaf, mustache 등 화면을 구성하기도 하고, Rest API로 서버가 구현된다면 json 응답으로 구성되기도 한다.)
  • Controller는 사용자의 요청을 처리하고 Model과 View를 중개하는 역할을 한다. Model과 View는 서로 연결되어 있지 않기 때문에 Controller가 사이에서 통신 매체가 되어준다.


  • DispatcherServlet : 클라이언트에게 요청을 받아 응답까지의 MVC 처리과정을 통제한다.
  • HandlerMapping : 클라이언트의 요청 URL을 어떤 Controller가 처리할지 결정한다.
  • HandlerAdapter : HandlerMapping에서 결정된 핸들러 정보로 해당 메소드를 직접 호출해주는 역할을 한다.
  • ViewResolver : Controller의 처리 결과(데이터)를 생성할 view를 결정한다.
  1. 클라이언트는 URL을 통해 요청을 전송한다.
  2. 디스패처 서블릿은 핸들러 매핑을 통해 해당 요청이 어느 컨트롤러에게 온 요청인지 찾는다.
  3. 디스패처 서블릿은 핸들러 어댑터에게 요청의 전달을 맡긴다.
  4. 핸들러 어댑터는 해당 컨트롤러에 요청을 전달한다.
  5. 컨트롤러는 비즈니스 로직을 처리한 후에 반환할 뷰의 이름을 반환한다.
  6. 디스패처 서블릿은 뷰 리졸버를 통해 반환할 뷰를 찾는다.
  7. 디스패처 서블릿은 컨트롤러에서 뷰에 전달할 데이터를 추가한다.
  8. 데이터가 추가된 뷰를 반환한다.

출처
https://ittrue.tistory.com/234
https://catsbi.oopy.io/f52511f3-1455-4a01-b8b7-f10875895d5b
https://velog.io/@do_dam/Spring-MVC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%8A%A4%ED%94%84%EB%A7%81-MVC-%EA%B5%AC%EC%A1%B0-%EC%9D%B4%ED%95%B4

0개의 댓글