[MVC-3] Spring MVC 구조 이해

2025. 12. 30. 18:36·Spring/MVC

1. 스프링 MVC 전체 구조

1.1 직접 만든 MVC 프레임워크와 스프링 MVC의 비교

 지난 시간에 단계적으로 진화시켜 만든 MVC 프레임워크 v5 버전과 스프링 MVC는 구조적으로 매우 유사하다. 이는 스프링 MVC가 웹 애플리케이션 개발에서 검증된 디자인 패턴과 아키텍처를 기반으로 구축되었기 때문이다.

직접 만든 MVC 프레임워크 구조
Spring MVC 구조

직접 만든 MVC 프레임워크  스프링 MVC 컴포넌트 역할
FrontControllerServletV5 DispatcherServlet 프론트 컨트롤러 역할
handlerMappingMap HandlerMapping 요청 URL을 핸들러(컨트롤러)로 매핑
MyHandlerAdapter HandlerAdapter 다양한 형태의 컨트롤러를 실행할 수 있는 어댑터
ModelView ModelAndView 모델 데이터와 뷰 정보를 담는 객체
MyView View 실제 뷰 렌더링을 담당
viewResolver ViewResolver 논리적 뷰 이름을 물리적 뷰 경로로 변환

1.2 DispatcherServlet의 구조와 등록 방식

DispatcherServlet은 스프링 MVC의 핵심 컴포넌트로 프론트 컨트롤러 패턴을 구현한 서블릿이다.

1.2.1 계층 구조

DispatcherServlet → FrameworkServlet → HttpServletBean → HttpServlet

이러한 상속 구조를 통해 DispatcherServlet은 서블릿으로 동작하면서도 스프링의 다양한 기능을 활용할 수 있다.

1.2.2 자동 등록 메커니즘

스프링 부트에서는 DispatcherServlet을 다음과 같은 방식으로 자동 등록한다:

  1. 모든 경로 매핑: urlPatterns="/" 설정으로 모든 요청을 처리
  2. 우선순위 규칙: 더 구체적인 경로를 가진 서블릿이 우선적으로 실행됨
  3. 기존 서블릿과의 공존: 기존에 등록된 서블릿과 함께 동작 가능

1.3 DispatcherServlet의 핵심 처리 흐름

DispatcherServlet의 요청 처리 과정은 doDispatch() 메서드에서 구현된다. 이 메서드는 다음과 같은 단계로 구성된다:

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());

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

1.4 뷰 렌더링 처리 과정

컨트롤러 실행 결과를 기반으로 최종 응답을 생성하는 과정은 다음과 같다:

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);
}

1.5 스프링 MVC의 전체 처리 흐름

Spring MVC 처리 흐름

  1. 핸들러 조회: HandlerMapping을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 찾음
  2. 핸들러 어댑터 조회: 찾은 핸들러를 실행할 수 있는 HandlerAdapter를 찾음
  3. 핸들러 어댑터 실행: 적합한 HandlerAdapter 실행
  4. 핸들러 실행: HandlerAdapter가 실제 핸들러(컨트롤러)를 호출
  5. ModelAndView 반환: 핸들러의 실행 결과를 ModelAndView 형식으로 변환
  6. ViewResolver 호출: 논리적 뷰 이름을 물리적 뷰 경로로 변환
  7. View 객체 반환: 실제 렌더링을 담당할 View 객체 반환
  8. 뷰 렌더링: View 객체를 통해 최종 응답 생성

1.6 확장 가능한 인터페이스 기반 설계

스프링 MVC의 가장 큰 장점은 DispatcherServlet의 코드를 수정하지 않고도 주요 컴포넌트를 교체하거나 확장할 수 있다는 점이다. 이를 가능하게 하는 주요 인터페이스들은 다음과 같다:

  • HandlerMapping: 요청을 처리할 핸들러를 찾는 전략 인터페이스
  • HandlerAdapter: 다양한 핸들러를 실행할 수 있는 어댑터 인터페이스
  • ViewResolver: 논리적 뷰 이름을 View 객체로 변환하는 인터페이스
  • View: 실제 응답을 생성하는 뷰 인터페이스

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

2.1 과거의 스프링 컨트롤러: Controller 인터페이스

스프링의 초기 버전에서는 Controller 인터페이스를 사용한 딱딱한 형식의 컨트롤러를 제공했다.

public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request,
                               HttpServletResponse response) throws Exception;
}

2.1.1 OldController 구현 예제

@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
                                     HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return null;
    }
}

이 컨트롤러는 @Component 애노테이션을 통해 /springmvc/old-controller라는 이름의 스프링 빈으로 등록된다. 빈 이름이 URL 매핑으로 사용되는 특징을 가진다.

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

스프링 부트는 애플리케이션 실행 시 다양한 HandlerMapping과 HandlerAdapter를 자동으로 등록한다. 이들은 우선순위에 따라 순차적으로 실행된다.

2.2.1 자동 등록되는 HandlerMapping 목록

  1. RequestMappingHandlerMapping: @RequestMapping 애노테이션 기반 컨트롤러 매핑
  2. BeanNameUrlHandlerMapping: 스프링 빈 이름을 URL로 매핑

2.2.2 자동 등록되는 HandlerAdapter 목록

  1. RequestMappingHandlerAdapter: @RequestMapping 애노테이션 기반 컨트롤러 처리
  2. HttpRequestHandlerAdapter: HttpRequestHandler 인터페이스 구현체 처리
  3. SimpleControllerHandlerAdapter: Controller 인터페이스 구현체 처리

2.3 OldController의 처리 과정

OldController가 요청을 처리하는 구체적인 과정은 다음과 같다:

2.3.1 핸들러 매핑 단계

  1. HandlerMapping을 순서대로 실행
  2. BeanNameUrlHandlerMapping이 /springmvc/old-controller 빈 이름과 매칭
  3. OldController 객체 반환

2.3.2 핸들러 어댑터 조회 단계

  1. HandlerAdapter의 supports() 메서드를 순차적으로 실행
  2. SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원함을 확인
  3. 해당 어댑터 선택

2.3.3 핸들러 실행 단계

  1. DispatcherServlet이 SimpleControllerHandlerAdapter 실행
  2. 어댑터가 내부에서 OldController.handleRequest() 호출
  3. 실행 결과 반환

2.4 HttpRequestHandler 인터페이스

HttpRequestHandler는 서블릿과 가장 유사한 형태의 핸들러 인터페이스이다.

public interface HttpRequestHandler {
    void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException;
}

2.4.1 MyHttpRequestHandler 구현 예제

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request,
                             HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHttpRequestHandler.handleRequest");
    }
}

2.4.2 MyHttpRequestHandler의 처리 과정

  1. 핸들러 매핑: BeanNameUrlHandlerMapping이 /springmvc/request-handler 빈 매칭
  2. 어댑터 조회: HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스 지원
  3. 핸들러 실행: 어댑터가 MyHttpRequestHandler.handleRequest() 실행

2.5 @RequestMapping 기반 컨트롤러

현대적인 스프링 MVC에서 가장 많이 사용되는 방식은 @RequestMapping 애노테이션 기반의 컨트롤러이다. 이를 처리하는 컴포넌트들은 다음과 같다:

  • HandlerMapping: RequestMappingHandlerMapping
  • HandlerAdapter: RequestMappingHandlerAdapter

이러한 컴포넌트들은 실무에서 99.9% 이상의 경우에 사용되며, 가장 높은 우선순위를 가진다.


3. 뷰 리졸버

3.1 뷰 리졸버의 역할과 필요성

 뷰 리졸버(ViewResolver)는 컨트롤러가 반환한 논리적 뷰 이름(예: "new-form")을 실제 물리적 뷰 경로(예: "/WEB-INF/views/new-form.jsp")로 변환하는 역할을 담당한다.

3.2 OldController에 뷰 반환 기능 추가

뷰 리졸버의 동작을 이해하기 위해 OldController를 다음과 같이 수정한다:

@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
                                     HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}

 컨트롤러가 "new-form"이라는 논리적 뷰 이름을 반환하지만, 초기 상태에서는 이를 처리할 뷰 리졸버 설정이 없어 Whitelabel Error Page가 표시된다.

3.3 뷰 리졸버 설정

스프링 부트에서 뷰 리졸버를 설정하는 방법은 다음과 같다:

3.3.1 application.properties 설정

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

이 설정은 스프링 부트가 자동으로 InternalResourceViewResolver를 등록할 때 사용된다.

3.3.2 전체 경로 직접 지정 (비권장)

return new ModelAndView("/WEB-INF/views/new-form.jsp");

이 방식은 뷰 리졸버의 장점을 활용하지 못하므로 권장되지 않는다.

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

스프링 부트는 다음과 같은 뷰 리졸버들을 자동으로 등록한다:

  1. BeanNameViewResolver: 빈 이름으로 뷰를 찾음 (예: 엑셀 파일 생성 기능)
  2. InternalResourceViewResolver: JSP 처리용 뷰 리졸버

3.5 뷰 리졸버의 동작 방식

뷰 리졸버의 구체적인 처리 과정은 다음과 같다:

  1. 핸들러 어댑터 실행: 핸들러 어댑터가 "new-form"이라는 논리적 뷰 이름 반환
  2. ViewResolver 순차 호출:
    • BeanNameViewResolver가 "new-form" 빈을 찾지만 존재하지 않음
    • InternalResourceViewResolver가 호출됨
  3. InternalResourceViewResolver 동작:
    • 접두사("/WEB-INF/views/")와 접미사(".jsp")를 추가
    • "/WEB-INF/views/new-form.jsp" 경로 생성
    • InternalResourceView 객체 반환
  4. View 렌더링: InternalResourceView가 forward()를 호출하여 JSP 실행

3.6 다양한 뷰 기술의 통합

3.6.1 JSP와 JSTL 처리

  • InternalResourceViewResolver는 JSTL 라이브러리가 존재하면 JstlView를 반환
  • JstlView는 기본적인 InternalResourceView 기능에 JSTL 태그 지원 기능 추가

3.6.2 뷰 렌더링 방식의 차이

  • JSP: forward()를 통해 해당 JSP로 이동 후 렌더링
  • 다른 뷰 템플릿(Thymeleaf 등): forward() 없이 바로 렌더링

3.6.3 타임리프(Thymeleaf) 통합

  • ThymeleafViewResolver를 등록하면 Thymeleaf 템플릿 사용 가능
  • 스프링 부트는 라이브러리만 추가하면 자동으로 설정을 구성

4. 스프링 MVC의 확장성과 실용성

4.1 인터페이스 기반의 유연한 설계

스프링 MVC의 가장 큰 강점은 확장성에 있다. 주요 컴포넌트들이 모두 인터페이스로 정의되어 있어 개발자가 필요에 따라 구현체를 교체하거나 확장할 수 있다.

4.2 다양한 컨트롤러 형태 지원

스프링 MVC는 역사적인 이유로 여러 형태의 컨트롤러를 지원한다:

  1. Controller 인터페이스: 스프링 초기의 전통적인 방식
  2. HttpRequestHandler 인터페이스: 서블릿과 유사한 저수준 방식
  3. @Controller 애노테이션: 현대적인 애노테이션 기반 방식

4.3 실무 적용 시 고려사항

4.3.1 대부분의 기능이 이미 구현됨

  • 스프링 MVC는 전 세계 개발자들의 요구사항을 반영하여 계속 발전
  • 웹 애플리케이션 개발에 필요한 대부분의 기능이 이미 구현되어 있음
  • 직접 확장할 필요가 거의 없음

4.3.2 문제 해결과 디버깅

  • 스프링 MVC의 내부 구조 이해는 문제 발생 시 원인 파악에 도움
  • 확장 포인트가 필요할 때 적절한 위치를 식별하는 데 유용

4.3.3 학습의 필요성

  • 복잡한 내부 구조를 완전히 파악하는 것은 어려움
  • 하지만 핵심 동작 방식을 이해하는 것은 중요함
  • 이는 문제 해결 능력과 시스템 이해도를 높여줌

4.4 스프링 MVC의 진화 과정

직접 만든 MVC 프레임워크의 진화 과정(v1~v5)은 스프링 MVC의 설계 철학을 이해하는 데 도움이 된다. 이 과정을 통해 다음과 같은 개념들을 자연스럽게 이해할 수 있다:

  1. 프론트 컨트롤러 패턴의 필요성과 장점
  2. 관심사 분리를 통한 코드 품질 향상
  3. 인터페이스와 어댑터 패턴의 유연성
  4. 확장 가능한 아키텍처의 설계 원칙

이러한 이해를 바탕으로 스프링 MVC를 효과적으로 활용하고, 필요시 확장할 수 있는 기반을 마련할 수 있다.

'Spring > MVC' 카테고리의 다른 글

[MVC-6] CSR 환경에서 @RequestBody만 쓸까? (feat. 파일 업로드와 @ModelAttribute)  (0) 2026.02.18
[MVC-5] 스프링 MVC 기본 기능  (0) 2025.12.30
[MVC-4] 스프링 MVC 시작하기: 애노테이션 기반 컨트롤러  (0) 2025.12.30
[MVC-2] 직접 만드는 MVC 프레임워크  (0) 2025.12.30
[MVC-1] 서블릿에서 JSP까지: 자바 백엔드 웹 기술의 발전과 한계  (0) 2025.12.30
'Spring/MVC' 카테고리의 다른 글
  • [MVC-5] 스프링 MVC 기본 기능
  • [MVC-4] 스프링 MVC 시작하기: 애노테이션 기반 컨트롤러
  • [MVC-2] 직접 만드는 MVC 프레임워크
  • [MVC-1] 서블릿에서 JSP까지: 자바 백엔드 웹 기술의 발전과 한계
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250)
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25)
        • MSA 기본 (11)
        • MSA 아키텍처 (14)
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[MVC-3] Spring MVC 구조 이해
상단으로

티스토리툴바