[Spring] MVC패턴과 디스패쳐 서블릿

imcool2551·2022년 2월 23일
0

Spring

목록 보기
8/15
post-thumbnail

본 글은 인프런 김영한님의 스프링 완전 정복 로드맵을 기반으로 정리했습니다.

1. 서블릿


이전 글에서 HTTP 요청 메시지 파싱, HTTP 응답 메시지 생성 등의 반복적이고 번거로운 작업을 서블릿의 편의 기능을 사용하여 처리할 수 있다고 했다.

서블릿이 매개변수로 받는 HttpServletRequest, HttpServletResponse 인터페이스는 위처럼 각각 요청 파라미터를 처리하고 응답 메시지의 타입/내용을 정하는 등 다양한 편의 기능을 제공한다. 이 외에도 HttpServletRequest는 요청 헤더 정보, 쿠키를 확인한다거나 HttpServletResponse는 응답 헤더 설정, 쿠키 설정, 리디렉션 등 웹 개발에 필요한 대부분의 기능을 정의하고 있다.

서블릿을 사용해서 비즈니스 로직 처리에만 집중할 수 있지만, 서블릿만 사용하면 코드중복이 심해진다. 물론 스프링 MVC의 편의 기능없이 서블릿만 사용해서 애플리케이션을 만들 수 있긴하다. 그러나, 요청 파라미터나 HTTP 메시지 바디를 처리하거나 응답 메시지를 생성하는 작업등등은 대부분의 비즈니스 로직에서 필요한 작업이다.

스프링MVC는 이런 반복적인 작업을 개발하기 편하게 프론트 컨트롤러 패턴인 디스패쳐 서블릿과 MVC 패턴을 도입했다. 이 두가지를 이해하는 것이 스프링MVC의 핵심이라고 할 수 있다.

2. MVC패턴


MVC 패턴의 핵심은 화면을 그리는 View 영역과 사용자 요청(로직)을 처리하는 Controller 영역을 분리하는 것이다. 둘을 분리하는 핵심적인 이유는 둘의 변경의 라이프 사이클이 다르다는 것이다. View 영역은 로직에 비해서 변경주기가 빠르다. 또한, 둘의 변경은 서로 독립적인 이유로 발생하는 경우가 많다. 각각 다른 주기와 이유로 변경되는데 둘을 분리하지 않고 하나의 파일에서 처리하려 하면 유지보수가 매우 힘들어진다.

둘을 분리해서 View 영역은 JSP/Thymeleaf와 같은 템플릿 엔진으로 처리하고 Controller에서 비즈니스 로직을 처리하게 하는 것이 더 좋은 아키텍쳐라고 할 수 있다. Controller는 비즈니스 로직을 처리한 후 View가 화면을 그릴 수 있도록 Model에 데이터를 담아서 전달한다. Model을 데이터 저장소라고 생각하면 된다. MVC패턴은 스프링뿐 아니라 다른 프레임워크에서도 적극 사용하는 패턴이다. 그림을 통해서 살펴보도록 하자.

  • MVC 패턴1

위에서 글로 설명한 MVC패턴 그대로다. 사용자의 요청이 들어오면 Controller에서 비즈니스 로직을 처리하고 화면을 그리는데 필요한 데이터를 Model에 담는다. View는 Model을 참조해서 화면을 생성한 뒤 사용자에게 응답한다. 뷰와 로직을 분리한것으로도 유지보수가 좋아지지만 더 많이 사용되는 패턴이 있다.

  • MVC 패턴2

MVC 패턴2는 컨트롤러에서 모든 로직을 수행하는 것을 단계적으로 분리한 것이다.

컨트롤러 계층은 로직처리 계층의 제일 앞단에 위치한다. 즉, 웹 영역에 대해서 알고 있다. 요청 파라미터의 유효성을 검증한다거나, 헤더를 확인하는 등의 작업을 처리한다.

서비스 계층은 웹 영역에 대해서 모른다. 도메인에 특화된 비즈니스 로직을 처리한다.

리포지토리 계층은 데이터 접근 계층으로 데이터를 영속화하고 조회/수정/삭제 하는 역할을 가진다.

뷰와 로직의 분리에 더해서 로직 처리도 여러 계층으로 분리하는 것이 유지보수 하기에 더욱 좋다.

3. 프론트 컨트롤러


MVC패턴2를 사용해서 뷰와 로직을 분리하고, 로직도 여러 계층으로 분리했다. 하지만, 아직 남은 문제는 서블릿만 사용해서 발생하는 코드 중복이다. 요청 파라미터를 처리등의 빈번한 작업을 공통 처리할 수 있다면 편리할 것이다. 그런 작업을 메서드로 분리하여 처리할 수도 있지만 여전히 헤당 메서드를 호출해야하는 중복이 있고, 실수로 호출하지 않으면 문제가 된다. 컨트롤러 호출 이전에 공통 작업을 처리하는 것이 필요하다.

공통 작업을 처리하기 위해 등장한 것이 프론트 컨트롤러(Front Controller)패턴이다. 스프링 MVC가 제공하는 편의 기능은 위에서 살펴본 MVC 패턴과 프론트 컨트롤러 패턴을 적용한 디스패쳐 서블릿(Dispatcher Servlet)을 근간으로 한다. MVC 패턴으로 계층을 분리해서 관심사를 나누고 디스패쳐 서블릿에서 컨트롤러 호출 이전에 공통 작업을 모두 처리함으로써 코드 중복을 줄아면 코드의 유지보수성이 좋아진다.

  • 프론트 컨트롤러 도입 이전

  • 프론트 컨트롤러 도입 이후

4. 디스패쳐 서블릿


공통 기능 처리를 편하게 하기 위한 패턴인 프론트 컨트롤러 패턴을 스프링은 디스패쳐 서블릿을 통해 제공한다고 했다. 디스패쳐 서블릿은 위의 그림처럼 사용자 요청을 처리한다.

DispatcherServlet의 계층구조를 따라가 보면 HttpServlet이 있다. 스프링 부트는 DispatcherServlet을 서블릿으로 자동 등록하고 모든 경로 urlPatterns="/"에대해 매핑한다. 그렇기 때문에 모든 요청에 대해서 공통 처리를 해줄 수 있는 것이다.

과정을 순서대로 하나씩 살펴보자.

  1. 핸들러 조회

    핸들러 매핑을 통해 사용자가 요청한 URL에 매핑된 핸들러를 조회한다. 핸들러는 사용자 요청을 핸들(처리)한다는 점에서 컨트롤러를 포함하는 상위 개념이지만 스프링 애플리케이션에서 대부분 사용자의 요청은 컨트롤러를 통해서 처리되므로 대부분의 경우 컨트롤러를 찾아 온다고 생각하면 된다.

  2. 핸들러 어댑터 조회

    핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.

    최근 대부분의 요청은 @Controller 애노테이션이 붙은 컨트롤러 클래스의 @RequestMapping 애노테이션이 붙은 메서드를 통해 처리된다.

    과거 자바에 애노테이션이 도입되기 전에 스프링은 ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse)메서드를 가지고 있는 org.springframework.web.servlet.mvc.Controller인터페이스를 구현해서 컨트롤러 클래스를 만들었었다.

    서블릿과 유사한 방식으로 요청을 처리하는 HttpRequestHandler인터페이스의 void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 를 구현해서 요청을 처리할 수도 있다.

    이처럼 스프링은 다양한 방식의 핸들러(컨트롤러)를 등록할 수 있다. 그럴일은 거의 없겠지만, 원한다면 사용자가 정의한 방식의 핸들러를 정의해서 사용자 요청을 처리할 수도 있다. 인터페이스를 기반으로 설계하여 OCP를 지킨 것이다.

    핸들러들은 각각의 처리 결과가 다르다 DispatcherServlet이 일관되게 처리결과를 반환받을 수 있기 위해서 어댑터 패턴이 도입되었다. 어떤 핸들러가 호출되던 DispatcherServletModelAndView를 반환받기 위해서 핸들러에 맞는 다양한 핸들러 어댑터가 필요하다. 스프링이 이미 필요한 어댑터를 대부분 구현해두었다.

  3. 핸들러 어댑터 실행

    핸들러 어댑터를 실행한다.

  4. 핸들러 실행

    핸들러 어댑터가 실제 핸들러를 실행한다.

  5. ModelAndView 반환

    핸들러 어댑터는 어떤 핸들러가 실행되던 일관되게 ModelAndView로 변환해서 반환한다.

  6. ViewResolver 호출

    뷰 리졸버를 찾고 실행한다. JSP, Thymeleaf 등 다양한 템플릿 엔진마다 실행되는 ViewResolver가 다르다. JSP를 사용한다면 application.properties 파일에 다음과 같은 설정을 추가해야 한다. 그러면 IntermalResourceViewResolver라는 이름의 뷰 리졸버가 동작한다.

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

    Thymeleaf의 경우엔 스프링 부트에 추가하면 자동으로 ThymeleafViewResolver가 등록되서 별도로 설정을 추가하지 않아도 된다.

  7. View 반환

    뷰 리졸버는 ModelAndView에 담겨있는 논리적인 뷰의 이름을 실제 물리 이름으로 바꿔준다.

  8. 뷰 렌더링

    뷰를 통해서 사용자가 볼 수 있는 화면을 렌더링 한다.

DispatcherServlet은 인터페이스를 통해 동작하기 때문에 코드의 변경없이 사용자가 원하는 기능을 확장할 수 있다. DIP, OCP를 지켜서 설계되었다고 볼 수 있다. 물론, 해당 인터페이스를 확장하기는 만만치 않고 대부분의 기능이 이미 구현되어 있기 때문에 그럴 필요 또한 없을 가능성이 크다. 주요 인터페이스 목록은 다음과 같다.

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

5. 정리


스프링 MVC의 핵심인 MVC패턴과 프론트 컨트롤러 패턴인 DispatcherServlet의 동작과정을 살펴봤다. 두 개념을 잘 정립하지 않고 스프링 MVC의 기능을 사용하면 깊이있는 이해가 불가능하다.

이제 두 개념을 알아봤으니 다음글에서 스프링MVC가 제공하는 편의기능이 얼마나 편하고 강력한지 코드를 통해 살펴보도록 하자. 스프링 MVC가 제공하는 기능은 매우 다양한데 이를 모두 살펴보는것은 불가능하고 실용적이지도 않으니 주로 사용되는 기능 위주로 살펴보도록 하겠다.

profile
아임쿨

0개의 댓글