Spring MVC 구조의 등장

oh_eol·2024년 4월 1일
0

Spring MVC

목록 보기
1/2

MVC 구조 이전

스프링 MVC 구조가 나오기까지 다음과 같은 점진적인 발전 과정이 있었다.

  • 버전 1
    • Servlet 으로만 개발했다.
    • view 를 위한 HTML 작업이 Java 코드에 섞이는 문제가 있었다.
  • 버전 2
    • JSP 로 개발했다.
    • 비즈니스 로직과 view 를 위한 HTML이 섞이는 문제는 여전히 남아 있었다.
  • 버전 3(현재의 방식)
    • 드디어 각각의 역할을 구분한 스프링 MVC 구조가 등장했다.
    • data를 다루는 Model, 화면을 그리는 View, http 요청을 받아 비즈니스 로직을 담당하는 Controller

그러나 MVC 구조에서도 여전히 코드의 중복과 불분명한 역할 등의 문제가 남아 있었다. 이를 보완하기 위해 등장한 것이 FrontController 패턴 이다. 본격적으로 살펴보자.


FrontController

FrontController 패턴의 특징

  • HttpServletRequest : 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받는다.
  • Controller Mapping : 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출한다.
  • 입구를 하나로 만드는 것이다!
  • 컨트롤러들의 공통 처리를 하여 코드 중복을 없앤다.
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.

FrontController 의 발전 5단계

Point : 중복을 제거하고, 변경 지점을 하나로 만들자!

후에 등장하는 DispatcherServlet을 더 잘 이해하기 위해서, 프론트 컨트롤러의 발전을 5단계로 간단하게 살펴보자.

1. 프론트 컨트롤러의 도입

  • 프론트 컨트롤러에서 controllerMap 을 이용하여 컨트롤러를 등록하거나 호출하는 기본적인 방식이다.
  • 각 컨트롤러들이 view로 이동하는 과정에 중복이 존재한다.

2. MyView 객체의 도입

  • MyView 객체는 뷰를 처리하는 객체이다.
  • 각 컨트롤러들은 MyView 객체를 생성해서 뷰 이름만 넣고 반환하고, FrontController 에서 반환받은 MyView 객체의 render() 메서드를 통해 이동 과정을 처리한다.
  • 여전히 컨트롤러들에 대한 서블릿 종속성이 존재한다.

3. viewResolver 의 등장

  • 서블릿 종속성 제거 : 프론트 컨트롤러에서 서블릿 처리를 완료하고, 요청 파라미터는 자바의 Map을 이용해서 각 컨트롤러에 넘긴다.
  • 뷰 이름 중복 제거 : 컨트롤러는 뷰의 논리 이름을 반환하고, 프론트 컨트롤러에서 viewResolver를 호출하여 실제 물리 위치의 이름을 처리한다.
  • 위의 두 기능을 담당할 ModelView 객체를 추가로 만들어주었다. ModelView는 논리 이름과 파라미터를 담당하는 객체이다.

앞서 나왔던 내용과 뒤에 나올 내용을 보다 이해하기 쉽게, 이 버전에 대해 좀 더 자세히 알아보자.
잠시 실제 개발자의 사고회로를 객체지향을 적용하며 상상해보았다.

구현 사고회로

  • 개발 순서는 다음과 같다.
    (I)컨트롤러 - (C)컨트롤러 - 프론트 컨트롤러
    각각에 역할과 책임을 나눠보자.

  • 컨트롤러 인터페이스
    일단 컨트롤러의 인터페이스로 컨트롤러의 원형을 만들자!

    • 서블릿은 프론트 컨트롤러가 알아서 처리한 뒤 paramMap 으로 파라미터를 넘겨줬을 것이다.
    • 이후 이를 상속받은 구체 컨트롤러 클래스에서 viewName과 data를 전달할 ModelView 객체를 생성하여 이를 반환할 것이다.
    • 위의 두 조건을 고려하여, 컨트롤러 인터페이스에서는 매개변수가 paramMap이고, 반환값이 ModelView인 process 메서드를 선언했다.
  • 컨트롤러
    인터페이스의 구현체로, process()를 오버라이드하여 구현해보자!

    • 전달받은 파라미터를 사용하고 비즈니스 로직을 구현한다.
    • ModelView 객체를 생성하여 논리 이름과 객체를 담고 반환한다.
  • 프론트 컨트롤러
    이전에 인터페이스를 만들며 정해둔 역할대로 구현하자!

    • 우선 controllerMap 에 컨트롤러들을 맵핑하자.
    • 서블릿의 service() 메서드를 오버라이드하고, 요청에 알맞은 컨트롤러 선택 및 파라미터를 paramMap으로 만들어 해당 컨트롤러에 전달한다.
    • viewResolver를 호출하고 컨트롤러 인터페이스가 넘겨준 ModelView를 이용하여 알맞은 view를 render 한다.

이렇게 각각의 역할을 짚어봤다. 발전 과정을 이어서 알아보자.

4. ModelView 대신 ViewName 을 반환

  • 각 컨트롤러가 항상 ModelView 를 반환하는 것은 번거롭다. 프론트 컨트롤러에서 모델 객체를 만들어 넘겨주면, 각 컨트롤러가 값을 담고 뷰의 논리 이름을 반환하도록 한다.

5. 유연한 Controller - HandlerAdapter

  • 컨트롤러 인터페이스를 다양하게 사용하기 위해, Adapter 패턴을 사용한다.
  • 핸들러 어댑터는 프론트 컨트롤러와 컨트롤러 사이에 존재한다.
  • 과거 프론트 컨트롤러가 컨트롤러에 대해 처리하던 로직을 핸들러 어댑터의 handle() 메서드에서 대신 처리한다.
  • 이제 프론트 컨트롤러는 각 핸들러(컨트롤러 인터페이스)에 맞는 핸들러 어댑터를 맵에 추가하고 등록한다.

지금까지 프론트 컨트롤러와 핸들러 어댑터 등등 직접 프레임워크를 구현하는 과정을 알아보고, 얼마나 수고스러운지 알게 되었다.
스프링 MVC는 DispatcherServlet을 제공하여, 이러한 개발자의 수고로움을 덜어준다.


DispatcherServlet

스프링 MVC 구조

  • 스프링 MVC의 프론트 컨트롤러인 DispatcherServlet
    프론트 컨트롤러는 핸들러 어댑터를 관리하고, 이 자리를 이어받은 디스패처 서블릿 또한 같은 일을 한다.
    일단 서블릿이 호출되면, 여러 메서드의 호출을 타고 DispatcherServlet.doDispatch() 가 호출된다.
  • doDispatch() 의 역할
    • 핸들러 조회
    • 핸들러 어댑터 조회
    • 핸들러 어댑터 실행과 이를 통한 핸들러 실행
    • 핸들러에서 반환받은 ModelAndView를 이용해서 viewResolver 호출
    • 반환받은 View의 render() 호출

앞서 살펴봤던 프론트 컨트롤러의 역할과 동일하다.


결론

실제로 개발할 때는 스프링이 제공하는 컨트롤러를 쓰게 될 것이고, 이는 어노테이션 기반으로 동작하기 때문에 이제껏 알아본 핸들러 어댑터 등을 따로 개발할 일은 거의 없을 것이다.
심지어 인터페이스를 상속받아 메서드 단위로 각각 만들었던 컨트롤러들도 하나의 클래스에 통합 가능하다.

그럼 이제까지 살펴본 스프링 MVC 내부의 동작방식이 실제로 어떻게 도움이 될까?

  • 나만의 작고 귀여운 컨트롤러 만들기 -> X
  • 문제 발생 시 원활한 원인파악과 해결 -> O

최종 결론 : @RequestMapping을 쓸 때마다 감사하며 개발하자.

profile
공부 중입니다.

0개의 댓글