스프링 MVC 구조 이해
- 김영한님의 스프링 MVC 1편 강의를 통해 직접 만든 MVC 프레임워크와 스프링 MVC를 비교하고,
DispatcherServlet의 구조와 동작 원리, 핸들러 매핑과 어댑터, 뷰 리졸버,@RequestMapping기반 컨트롤러의 발전 과정을 정리함
직접 만든 MVC 프레임워크와 스프링 MVC 비교
용어 대응 관계
| 직접 만든 프레임워크 | 스프링 MVC |
|---|---|
FrontController |
DispatcherServlet |
handlerMappingMap |
HandlerMapping |
MyHandlerAdapter |
HandlerAdapter |
ModelView |
ModelAndView |
viewResolver |
ViewResolver |
MyView |
View |
개념
- 스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어 있음
DispatcherServlet이 프론트 컨트롤러 역할을 담당
DispatcherServlet 구조
상속 계층

서블릿 등록
- 스프링 부트가
DispatcherServlet을 서블릿으로 자동 등록 urlPatterns="/"- 모든 경로에 대해 매핑됨
- 우선순위
- 더 자세한 경로가 우선순위가 높음
- 기존 서블릿도 함께 동작 가능
요청 처리 흐름
HttpServlet의service()호출FrameworkServlet에서service()오버라이드- 여러 메서드 호출을 거쳐
DispatcherServlet.doDispatch()호출
DispatcherServlet 로직
doDispatch() 메서드 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
// 핸들러 조회
mappedHandler = getHandler(processedRequest);
// 핸들러 어댑터 조회
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 핸들러 어댑터 실행 -> 핸들러 실행 -> ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 뷰 렌더링
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
동작 순서

단계별 설명
- 1. 핸들러 조회
- 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회
- 2. 핸들러 어댑터 조회
- 핸들러를 실행할 수 있는 핸들러 어댑터를 조회
- 3. 핸들러 어댑터 실행
- 조회한 핸들러 어댑터를 실행
- 4. 핸들러 실행
- 핸들러 어댑터가 실제 핸들러를 실행
- 5.
ModelAndView반환- 핸들러 어댑터는 핸들러가 반환하는 정보를
ModelAndView로 변환하여 반환
- 핸들러 어댑터는 핸들러가 반환하는 정보를
- 6.
ViewResolver호출- 뷰 리졸버를 찾아서 실행
- JSP의 경우
InternalResourceViewResolver가 자동 등록됨
- 7.
View반환- 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 변환
- 렌더링 역할을 담당하는 뷰 객체를 반환
- JSP의 경우 내부에
forward()로직이 있는InternalResourceView(JstlView)를 반환
- 8. 뷰 렌더링
- 뷰를 통해서 뷰를 렌더링
주요 인터페이스
확장 가능한 설계
DispatcherServlet코드 변경 없이 원하는 기능을 변경하거나 확장할 수 있음
인터페이스 목록
HandlerMappingorg.springframework.web.servlet.HandlerMapping
HandlerAdapterorg.springframework.web.servlet.HandlerAdapter
ViewResolverorg.springframework.web.servlet.ViewResolver
Vieworg.springframework.web.servlet.View
핸들러 매핑과 핸들러 어댑터
스프링 부트 자동 등록 목록
-
HandlerMapping(우선순위 순)1
0 = RequestMappingHandlerMapping
- @RequestMapping 애노테이션 기반 컨트롤러
1
1 = BeanNameUrlHandlerMapping
- 스프링 빈의 이름으로 핸들러 찾기
-
HandlerAdapter(우선순위 순)1
0 = RequestMappingHandlerAdapter
- @RequestMapping 애노테이션 기반 컨트롤러
1
1 = HttpRequestHandlerAdapter
- HttpRequestHandler 인터페이스 처리
1
2 = SimpleControllerHandlerAdapter
- Controller 인터페이스 처리 (과거 방식)
- @RequestMapping 애노테이션 기반 컨트롤러
동작 방식
- 핸들러 매핑과 핸들러 어댑터 모두 순서대로 찾음
- 없으면 다음 순서로 넘어감
과거 방식 컨트롤러 예제
Controller 인터페이스 방식
1
2
3
4
5
6
7
8
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
System.out.println("OldController.handleRequest");
return null;
}
}
-
처리 과정

- 핸들러 매핑으로 핸들러 조회
BeanNameUrlHandlerMapping이 빈 이름으로 핸들러를 찾아OldController반환
- 핸들러 어댑터 조회
SimpleControllerHandlerAdapter가Controller인터페이스를 지원
- 핸들러 어댑터 실행
SimpleControllerHandlerAdapter가OldController를 실행하고 결과 반환
- 핸들러 매핑으로 핸들러 조회
HttpRequestHandler 방식
1
2
3
4
5
6
7
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
System.out.println("MyHttpRequestHandler.handleRequest");
}
}
-
처리 과정
- 핸들러 매핑
BeanNameUrlHandlerMapping이MyHttpRequestHandler반환
- 핸들러 어댑터 조회
HttpRequestHandlerAdapter가HttpRequestHandler인터페이스 지원
- 핸들러 어댑터 실행
HttpRequestHandlerAdapter가 핸들러 실행
- 핸들러 매핑
현대적 방식
- 실무에서는 99.9%
@RequestMapping기반 컨트롤러를 사용 RequestMappingHandlerMapping과RequestMappingHandlerAdapter가 처리
뷰 리졸버
설정 방법
1
2
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
- 스프링 부트는 이 설정으로
InternalResourceViewResolver를 자동 등록
스프링 부트가 자동 등록하는 뷰 리졸버
1
1 = BeanNameViewResolver
- 빈 이름으로 뷰 찾기 (예: 엑셀 파일 생성)
1
2 = InternalResourceViewResolver
- JSP 처리 가능한 뷰 반환
동작 과정

- 핸들러 어댑터 호출
- 논리 뷰 이름
new-form획득
- 논리 뷰 이름
ViewResolver호출BeanNameViewResolvernew-form이름의 스프링 빈 찾기 실패
InternalResourceViewResolver호출
InternalResourceViewResolverInternalResourceView반환
- 뷰
InternalResourceView- JSP처럼
forward()를 호출해서 처리할 수 있는 경우에 사용
- JSP처럼
view.render()InternalResourceView가forward()를 사용하여 JSP 실행
참고사항
- JSTL 라이브러리가 있으면
JstlView를 반환 - JSP는
forward()를 통해 이동해야 렌더링됨 - 다른 뷰 템플릿은
forward()없이 바로 렌더링됨 - Thymeleaf는 라이브러리만 추가하면 스프링 부트가 자동 설정
@RequestMapping 기반 컨트롤러
도입 배경
- 과거 스프링은 MVC 부분이 약해 스트럿츠 같은 프레임워크를 사용
@RequestMapping기반 컨트롤러 등장 이후 스프링의 완승으로 끝남
구성요소
RequestMappingHandlerMapping- 핸들러 매핑
RequestMappingHandlerAdapter- 핸들러 어댑터
- 실무에서 99.9% 이 방식 사용
V1: 기본 @RequestMapping 방식
회원 등록 폼
-
기본 구조
@Controller+@RequestMapping을 사용한 가장 기본적인 형태- 각 기능마다 별도의 컨트롤러 클래스를 생성
ModelAndView객체를 생성하여 반환
주요 애노테이션
@Controller- 스프링이 자동으로 스프링 빈으로 등록 (내부에
@Component포함) - 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식
- 스프링이 자동으로 스프링 빈으로 등록 (내부에
@RequestMapping- 요청 정보를 매핑하여 해당 URL 호출 시 메서드 실행
- 애노테이션 기반이므로 메서드 이름은 임의로 지정 가능
ModelAndView- 모델과 뷰 정보를 담아서 반환
컨트롤러 인식 조건
RequestMappingHandlerMapping은 다음 조건의 스프링 빈을 매핑 정보로 인식@RequestMapping또는@Controller가 클래스 레벨에 있는 경우
대체 방식
-
컴포넌트 스캔 사용
1 2 3
@Component @RequestMapping public class SpringMemberFormControllerV1 { }
-
직접 빈 등록
1 2 3 4
@Bean SpringMemberFormControllerV1 springMemberFormControllerV1() { return new SpringMemberFormControllerV1(); }
스프링 3.0 이상 주의사항
- 스프링 부트 3.0부터는 클래스 레벨에
@Controller가 반드시 필요함@RequestMapping만으로는 인식되지 않음
회원 저장
-
mv.addObject()- 모델 데이터 추가 시 사용하며, 뷰 렌더링 시 활용됨
회원 목록
- 특징
HttpServletRequest,HttpServletResponse를 직접 사용ModelAndView에 모델 데이터와 뷰 이름을 담아서 반환- 각 URL마다 별도의 컨트롤러 클래스 필요 (회원 등록 폼, 회원 저장, 회원 목록)
- V1 전체 코드 보기
V2: 컨트롤러 통합
아이디어
@RequestMapping은 메서드 단위로 적용됨- 컨트롤러 클래스를 하나로 통합할 수 있음
주요 개선점
- 클래스 레벨에
@RequestMapping("/springmvc/v2/members")추가 - 관련 기능들을 하나의 컨트롤러 클래스로 통합
-
메서드 레벨의
@RequestMapping과 조합되어 최종 URL 결정 -
구조 예시
1 2 3 4 5 6 7 8 9 10 11 12
@Controller @RequestMapping("/springmvc/v2/members") // 클래스 레벨 public class SpringMemberControllerV2 { @RequestMapping("/new-form") // → /springmvc/v2/members/new-form public ModelAndView newForm() { } @RequestMapping("/save") // → /springmvc/v2/members/save public ModelAndView save() { } @RequestMapping // → /springmvc/v2/members public ModelAndView members() { } }
- V2 전체 코드 보기
조합 방식
- 클래스 레벨과 메서드 레벨의
@RequestMapping이 조합됨

V3: 실용적인 방식 (실무 표준)
개선 사항
Model도입ViewName직접 반환@RequestParam사용@GetMapping,@PostMapping도입
주요 개선점
-
구조 예시
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
@Controller @RequestMapping("/springmvc/v3/members") public class SpringMemberControllerV3 { @GetMapping("/new-form") public String newForm() { return "new-form"; // ViewName 직접 반환 } @PostMapping("/save") public String save( @RequestParam("username") String username, // @RequestParam 사용 @RequestParam("age") int age, Model model) { // Model 파라미터로 받음 Member member = new Member(username, age); memberRepository.save(member); model.addAttribute("member", member); return "save-result"; } @GetMapping public String members(Model model) { List<Member> members = memberRepository.findAll(); model.addAttribute("members", members); return "members"; } }
주요 개선 내용
Model파라미터- 스프링이
Model객체를 파라미터로 제공하여 편리하게 사용할 수 있음
- 스프링이
ViewName직접 반환ModelAndView대신 뷰의 논리 이름을String으로 직접 반환
@RequestParam- HTTP 요청 파라미터를 받을 수 있음
@RequestParam("username")은request.getParameter("username")과 동일- GET 쿼리 파라미터, POST Form 모두 지원
HTTP Method 구분
-
기본 방식
1
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
-
편리한 방식
@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping- 이들은 내부적으로
@RequestMapping애노테이션을 포함하고 있음
연습 문제
-
스프링 MVC에서 HTTP 요청을 가장 먼저 받아 처리하는 핵심 컴포넌트는 무엇일까요?
a.
DispatcherServlet- 스프링 MVC에서 모든 HTTP 요청은 이 컴포넌트를 통해 들어옴
- 여기서 요청을 하나의 진입점에서 전달받은 후,
HandlerMapping이나HandlerAdapter등에 차례로 위임함
-
DispatcherServlet이후, 요청 처리를 위해 일반적으로Handler를 찾고 실행한 뒤View를 찾는 과정에서 핵심적인 순서는 무엇일까요?a.
HandlerMapping→HandlerAdapter→ViewResolverDispatcherServlet은 요청을 받으면HandlerMapping으로Handler를 찾고,HandlerAdapter로 실행함- 실행 결과로
ViewResolver가 실제View를 찾음
-
Spring MVC에서 요청 URL을 처리할
Handler(Controller)를 찾는 역할을 분리하여 담당하는 두 컴포넌트는 각각 무엇일까요?a.
HandlerMapping,HandlerAdapter- 요청 URL에 맞는
Handler를 찾는 것은HandlerMapping이, 이Handler의 타입에 상관없이 실행하는 것은HandlerAdapter의 역할임 - 이 둘이 함께 동작함
- 요청 URL에 맞는
-
Handler실행 후 반환된 논리적인View이름을 실제View(예: JSP 파일)로 변환하여 찾아내는 역할을 하는 컴포넌트는 무엇일까요?a.
ViewResolverController가 반환한View이름(논리적 이름)을 가지고 실제View템플릿 객체(예: JSP)를 찾아주는 역할을ViewResolver가 담당함
-
현대적인 Spring MVC 개발에서
@Controller,@GetMapping,@PostMapping과 같은 애노테이션은 주로 어떤 역할을 가능하게 할까요?a. HTTP 요청과
Handler/Method매핑 및 처리 방식 정의- 이 애노테이션들은 특정 URL 패턴의 HTTP 요청(GET/POST 등)을 어떤
Controller클래스의 어떤Method가 처리할지 편리하게 연결(매핑)하고 그 방식을 정의해줌
- 이 애노테이션들은 특정 URL 패턴의 HTTP 요청(GET/POST 등)을 어떤
요약 정리
- 프론트 컨트롤러 패턴과 스프링 MVC
- 직접 만든 프레임워크의
FrontController가 스프링 MVC의DispatcherServlet에 대응 HandlerMapping,HandlerAdapter,ViewResolver등의 인터페이스로 확장 가능한 구조
- 직접 만든 프레임워크의
DispatcherServlet동작 순서- 핸들러 조회 (
HandlerMapping) - 핸들러 어댑터 조회 (
HandlerAdapter) - 핸들러 어댑터 실행 → 핸들러 실행
ModelAndView반환ViewResolver호출View반환- 뷰 렌더링
- 핸들러 조회 (
- 핸들러 매핑과 어댑터
RequestMappingHandlerMapping/RequestMappingHandlerAdapter@RequestMapping기반 (실무 99.9%)
BeanNameUrlHandlerMapping/SimpleControllerHandlerAdapter- 과거 방식
- 뷰 리졸버
BeanNameViewResolver- 빈 이름으로 뷰 찾기
InternalResourceViewResolver- JSP 처리
spring.mvc.view.prefix/suffix설정으로 자동 등록
@RequestMapping발전 과정- V1: 기본 방식
@Controller+@RequestMappingModelAndView반환
- V2: 컨트롤러 통합
- 클래스 레벨
@RequestMapping으로 경로 조합
- 클래스 레벨
- V3: 실용적 방식 (실무 표준)
Model파라미터String반환 (뷰 이름)@RequestParam@GetMapping,@PostMapping
- V1: 기본 방식
- 원칙
- 인터페이스 기반 설계
- 코드 변경 없이 기능 확장 가능
- 점진적 발전
- V1 → V2 → V3로 실용성 향상
- 프레임워크의 역할
- 복잡한 처리는 프레임워크가 담당
- 개발자는 비즈니스 로직에 집중
- 인터페이스 기반 설계