Spring

[Spring] MVC 11편 - 스프링 MVC 구조 완전 이해

dev-nadan 2025. 8. 13. 14:59

이번 편에서는 우리가 직접 만들었던 MVC 프레임워크와 스프링 MVC의 구조를 비교하고, 스프링 MVC가 어떻게 요청을 처리하는지 흐름을 단계별로 살펴본다. 특히 DispatcherServlet을 중심으로 한 핵심 컴포넌트, 인터페이스 구조, 그리고 실무에서 주로 사용하는 컨트롤러 작성 방식을 정리한다.


1. 스프링 MVC 전체 구조

 

우리가 만든 MVC 프레임워크는 FrontController, handlerMappingMap, MyHandlerAdapter 등을 사용했는데, 스프링 MVC에서는 이를 다음과 같이 확장·구체화한다.

직접 구현 Spring MVC
FrontController DispatcherServlet
handlerMappingMap HandlerMapping
MyHandlerAdapter HandlerAdapter
ModelView ModelAndView
viewResolver ViewResolver
MyView View

 

핵심

  • DispatcherServlet이 모든 요청을 받아 처리하는 프론트 컨트롤러 역할을 한다.
  • 각 단계는 인터페이스 기반으로 설계되어 있어 기능 확장에 유연하다.

2. DispatcherServlet 요청 흐름

  1. 핸들러 조회 → HandlerMapping을 통해 URL과 매핑된 컨트롤러 찾기
  2. 핸들러 어댑터 조회 → 해당 컨트롤러를 실행할 수 있는 Adapter 찾기
  3. 핸들러 어댑터 실행 → 컨트롤러 호출
  4. ModelAndView 반환 → 모델과 뷰 정보 함께 전달
  5. ViewResolver 호출 → 논리 뷰 이름을 물리 뷰 경로로 변환
  6. 뷰 렌더링 → JSP, Thymeleaf 등으로 최종 응답 생성

3. 핸들러 매핑과 어댑터

스프링 부트 기본 등록 목록(중요 부분만)

  • HandlerMapping
    • RequestMappingHandlerMapping → @RequestMapping 기반 컨트롤러
    • BeanNameUrlHandlerMapping → 빈 이름으로 컨트롤러 검색
  • HandlerAdapter
    • RequestMappingHandlerAdapter → @RequestMapping 기반
    • HttpRequestHandlerAdapter → HttpRequestHandler 기반
    • SimpleControllerHandlerAdapter → 옛날 Controller 인터페이스 기반

이 구조 덕분에 다양한 형태의 컨트롤러를 유연하게 처리 가능하다.


4. 뷰 리졸버

  • InternalResourceViewResolver
    • JSP 처리 전용 뷰 리졸버
    • prefix, suffix 설정으로 논리 뷰 이름을 JSP 경로로 변환
  • 동작 순서
    1. 핸들러 어댑터로부터 논리 뷰 이름 획득
    2. ViewResolver가 해당 이름을 실제 뷰 객체로 변환
    3. JSP의 경우 forward() 호출로 렌더링

5. @RequestMapping 기반 컨트롤러

실무에서 99% 이상 사용하는 방식

@Controller
@RequestMapping("/springmvc/v3/members")
public class MemberController {

    @GetMapping("/new-form")
    public String newForm() {
        return "new-form";
    }

    @PostMapping("/save")
    public String save(@RequestParam String username,
                       @RequestParam int age,
                       Model model) {
        Member member = new Member(username, age);
        model.addAttribute("member", member);
        return "save-result";
    }
}

 

  • 장점
    • @GetMapping, @PostMapping으로 HTTP Method 구분 가능
    • @RequestParam으로 파라미터 바인딩 편리
    • Model 파라미터로 뷰 렌더링 데이터 전달

6. 구조적 진화

  • V1: 각각의 컨트롤러 클래스 분리, ModelAndView 직접 반환
  • V2: 클래스 레벨 @RequestMapping으로 URL 공통 부분 제거
  • V3: Model 파라미터, 뷰 이름 직접 반환, 파라미터 바인딩 간소화

회고

이번에 스프링 MVC 구조를 처음부터 뜯어보면서, 단순히 @Controller만 쓰던 시절에는 몰랐던 DispatcherServlet 내부 동작 원리를 제대로 이해하게 됐다. 예전 프로젝트에서 URL 매핑이 안 되는 문제나, 뷰 경로가 잘못 나오는 문제를 디버깅할 때 왜 HandlerMappingViewResolver 로그를 확인해야 하는지 이유를 확실히 알았다. 앞으로는 스프링이 자동으로 해주는 일들 뒤에 어떤 컴포넌트들이 동작하는지 감을 잡고, 필요할 때는 확장 포인트를 자신 있게 활용할 수 있을 것 같다.

 


 

스프링 MVC 구조 도식화

[클라이언트]
    │  요청 (HTTP)
    ▼
[DispatcherServlet]  ← 프론트 컨트롤러
    │
    │  1. HandlerMapping 목록 조회
    ▼
[HandlerMapping] ----------------------
    │  URL → 컨트롤러 객체 매핑
    └─ 예: RequestMappingHandlerMapping
           BeanNameUrlHandlerMapping
----------------------------------------
    │
    │  2. HandlerAdapter 목록 조회
    ▼
[HandlerAdapter] -----------------------
    │  컨트롤러 실행 가능 여부 판단
    └─ 예: RequestMappingHandlerAdapter
           HttpRequestHandlerAdapter
           SimpleControllerHandlerAdapter
----------------------------------------
    │
    │  3. HandlerAdapter 실행
    ▼
[Controller] ---------------------------
    │  비즈니스 로직 실행
    │  Model + View 정보 반환
----------------------------------------
    │
    │  4. ViewResolver 목록 조회
    ▼
[ViewResolver] -------------------------
    │  논리 뷰 이름 → 물리 뷰 경로 변환
    └─ 예: InternalResourceViewResolver
----------------------------------------
    │
    │  5. View 렌더링
    ▼
[View] ---------------------------------
    │  JSP, Thymeleaf, JSON 응답 등
----------------------------------------
    │
    ▼
[클라이언트]  ← HTML/JSON 응답 반환