1. 스프링 MVC 전체 구조
1.1 직접 만든 MVC 프레임워크와 스프링 MVC의 비교
지난 시간에 단계적으로 진화시켜 만든 MVC 프레임워크 v5 버전과 스프링 MVC는 구조적으로 매우 유사하다. 이는 스프링 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을 다음과 같은 방식으로 자동 등록한다:
- 모든 경로 매핑: urlPatterns="/" 설정으로 모든 요청을 처리
- 우선순위 규칙: 더 구체적인 경로를 가진 서블릿이 우선적으로 실행됨
- 기존 서블릿과의 공존: 기존에 등록된 서블릿과 함께 동작 가능
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의 전체 처리 흐름

- 핸들러 조회: HandlerMapping을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 찾음
- 핸들러 어댑터 조회: 찾은 핸들러를 실행할 수 있는 HandlerAdapter를 찾음
- 핸들러 어댑터 실행: 적합한 HandlerAdapter 실행
- 핸들러 실행: HandlerAdapter가 실제 핸들러(컨트롤러)를 호출
- ModelAndView 반환: 핸들러의 실행 결과를 ModelAndView 형식으로 변환
- ViewResolver 호출: 논리적 뷰 이름을 물리적 뷰 경로로 변환
- View 객체 반환: 실제 렌더링을 담당할 View 객체 반환
- 뷰 렌더링: 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 목록
- RequestMappingHandlerMapping: @RequestMapping 애노테이션 기반 컨트롤러 매핑
- BeanNameUrlHandlerMapping: 스프링 빈 이름을 URL로 매핑
2.2.2 자동 등록되는 HandlerAdapter 목록
- RequestMappingHandlerAdapter: @RequestMapping 애노테이션 기반 컨트롤러 처리
- HttpRequestHandlerAdapter: HttpRequestHandler 인터페이스 구현체 처리
- SimpleControllerHandlerAdapter: Controller 인터페이스 구현체 처리
2.3 OldController의 처리 과정
OldController가 요청을 처리하는 구체적인 과정은 다음과 같다:
2.3.1 핸들러 매핑 단계
- HandlerMapping을 순서대로 실행
- BeanNameUrlHandlerMapping이 /springmvc/old-controller 빈 이름과 매칭
- OldController 객체 반환
2.3.2 핸들러 어댑터 조회 단계
- HandlerAdapter의 supports() 메서드를 순차적으로 실행
- SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원함을 확인
- 해당 어댑터 선택
2.3.3 핸들러 실행 단계
- DispatcherServlet이 SimpleControllerHandlerAdapter 실행
- 어댑터가 내부에서 OldController.handleRequest() 호출
- 실행 결과 반환
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의 처리 과정
- 핸들러 매핑: BeanNameUrlHandlerMapping이 /springmvc/request-handler 빈 매칭
- 어댑터 조회: HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스 지원
- 핸들러 실행: 어댑터가 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 스프링 부트가 자동 등록하는 뷰 리졸버
스프링 부트는 다음과 같은 뷰 리졸버들을 자동으로 등록한다:
- BeanNameViewResolver: 빈 이름으로 뷰를 찾음 (예: 엑셀 파일 생성 기능)
- InternalResourceViewResolver: JSP 처리용 뷰 리졸버
3.5 뷰 리졸버의 동작 방식

뷰 리졸버의 구체적인 처리 과정은 다음과 같다:
- 핸들러 어댑터 실행: 핸들러 어댑터가 "new-form"이라는 논리적 뷰 이름 반환
- ViewResolver 순차 호출:
- BeanNameViewResolver가 "new-form" 빈을 찾지만 존재하지 않음
- InternalResourceViewResolver가 호출됨
- InternalResourceViewResolver 동작:
- 접두사("/WEB-INF/views/")와 접미사(".jsp")를 추가
- "/WEB-INF/views/new-form.jsp" 경로 생성
- InternalResourceView 객체 반환
- 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는 역사적인 이유로 여러 형태의 컨트롤러를 지원한다:
- Controller 인터페이스: 스프링 초기의 전통적인 방식
- HttpRequestHandler 인터페이스: 서블릿과 유사한 저수준 방식
- @Controller 애노테이션: 현대적인 애노테이션 기반 방식
4.3 실무 적용 시 고려사항
4.3.1 대부분의 기능이 이미 구현됨
- 스프링 MVC는 전 세계 개발자들의 요구사항을 반영하여 계속 발전
- 웹 애플리케이션 개발에 필요한 대부분의 기능이 이미 구현되어 있음
- 직접 확장할 필요가 거의 없음
4.3.2 문제 해결과 디버깅
- 스프링 MVC의 내부 구조 이해는 문제 발생 시 원인 파악에 도움
- 확장 포인트가 필요할 때 적절한 위치를 식별하는 데 유용
4.3.3 학습의 필요성
- 복잡한 내부 구조를 완전히 파악하는 것은 어려움
- 하지만 핵심 동작 방식을 이해하는 것은 중요함
- 이는 문제 해결 능력과 시스템 이해도를 높여줌
4.4 스프링 MVC의 진화 과정
직접 만든 MVC 프레임워크의 진화 과정(v1~v5)은 스프링 MVC의 설계 철학을 이해하는 데 도움이 된다. 이 과정을 통해 다음과 같은 개념들을 자연스럽게 이해할 수 있다:
- 프론트 컨트롤러 패턴의 필요성과 장점
- 관심사 분리를 통한 코드 품질 향상
- 인터페이스와 어댑터 패턴의 유연성
- 확장 가능한 아키텍처의 설계 원칙
이러한 이해를 바탕으로 스프링 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 |
