Servlet으로도 간단한 API는 만들 수 있다. 예를 들어 영화 추천 API처럼 요청을 받고, 영화 목록에서 하나를 고르고, JSON으로 변환한 뒤 응답을 보내는 코드는 정상적으로 동작한다. 문제는 기능이 많아질 때 시작된다. Servlet 하나 안에 HTTP 요청 처리, 비즈니스 로직, 응답 설정, JSON 변환 코드가 함께 들어가면 클래스 하나가 너무 많은 책임을 가지게 된다. 이 구조는 이해하기 어렵고, 테스트하기도 어렵고, 비슷한 코드가 계속 반복된다.
이 문제를 해결하는 핵심은 역할 분리다.
음식점으로 비유하면, 주문 접수, 요리, 서빙, 계산을 한 사람이 전부 처리하는 구조에서 벗어나 각자 맡은 일을 나누는 것이다. Spring MVC는 이 역할을 크게 Controller, Model, View로 나누어 관리한다.
| 구성 요소 | 역할 |
|---|---|
| Controller | 사용자의 요청을 받고, 필요한 로직을 호출하며, 결과를 Model에 담고 View를 선택한다. |
| Model | Controller에서 View로 전달할 데이터를 key-value 형태로 담는다. |
| View | Model의 데이터를 사용해 사용자에게 보여줄 화면을 만든다. |
MVC 패턴의 핵심은 단순히 파일을 나누는 것이 아니라, 변경의 영향을 줄이는 구조를 만드는 것이다. 화면이 바뀌면 View를 중심으로 수정하고, 요청 처리 방식이 바뀌면 Controller를 중심으로 수정할 수 있다. 덕분에 코드가 커질수록 유지보수성은 더 좋아진다.

사용자의 요청이 Controller로 들어오고, Controller가 Service/Repository와 View를 연결하며, Model이 View에 전달할 데이터를 담는 흐름을 보여준다.
MVC를 처음 보면 "Controller가 요청을 받는다"는 설명은 이해된다. 하지만 여기서 의문이 생겼다.
사용자의 요청이 오면 Controller가 받는다고 하는데, 실제로는 어떻게 해당 Controller까지 도착하는가??
Front Controller Pattern은 모든 요청을 하나의 공통 진입점에서 먼저 받는 구조다. 각각의 Controller가 공통 작업을 반복해서 처리하는 것이 아니라, Front Controller가 요청을 먼저 받고 필요한 Controller로 위임한다. 이 구조를 사용하면 인증, 로깅, 인코딩, 공통 예외 처리 같은 작업을 중앙에서 관리할 수 있다.
| 기존 방식 | Front Controller 방식 |
|---|---|
| 각 Controller마다 공통 로직이 반복된다. | 공통 로직을 한 곳에서 처리한다. |
| 기능이 늘어나면 수정 지점이 많아진다. | 요청 흐름이 일관된다. |
| 관리가 어렵고 중복이 많다. | 유지보수성이 좋아진다. |
Spring MVC에서는 이 Front Controller 역할을 DispatcherServlet이 담당한다.
DispatcherServlet은 요청을 가장 먼저 받고, 어떤 Controller가 처리해야 하는지 찾고, 실행을 위임하고, 최종 응답까지 조율한다. 쉽게 말하면 Spring MVC 요청 흐름의 “안내 데스크”이자 “교통정리 담당자”다.

여러 클라이언트 요청이 Front Controller로 모이고, Front Controller가 요청에 맞는 Controller로 작업을 위임하는 구조를 보여준다.
Front Controller가 모든 요청을 먼저 받는다고 해서 문제가 전부 해결되는 것은 아니다. Spring은 여러 형태의 Controller를 지원한다. @Controller 어노테이션 방식, Controller 인터페이스 방식, HttpRequestHandler 방식처럼 Controller의 모양이 서로 다를 수 있다. Front Controller가 이 모든 방식을 직접 알고 처리하려고 하면 다시 복잡해진다.
이때 필요한 것이 Adapter Pattern이다. 어댑터는 서로 다른 모양의 대상을 공통된 방식으로 사용할 수 있게 연결해주는 역할을 한다. 콘센트 모양이 다른 전자기기를 변환 어댑터로 연결하는 것과 비슷하다.
Spring MVC에서는 HandlerAdapter가 이 역할을 한다.
| 개념 | 역할 |
|---|---|
| HandlerMapping | 요청 URL에 맞는 Controller를 찾아준다. |
| HandlerAdapter | 찾은 Controller를 실행할 수 있는 방식으로 맞춰준다. |
| Controller | 실제 요청 처리 로직을 수행한다. |
중요한 점은 DispatcherServlet이 Controller를 무작정 직접 실행하는 것이 아니라, HandlerAdapter를 통해 실행을 위임한다는 것이다. 이렇게 하면 Controller의 구현 방식이 달라도 DispatcherServlet은 일관된 흐름으로 요청을 처리할 수 있다. 새로운 Controller 방식이 추가되더라도 새로운 Adapter만 추가하면 되므로 기존 구조를 크게 바꾸지 않아도 된다.

Front Controller가 HandlerAdapter 목록에서 적절한 Adapter를 선택하고, 선택된 Adapter가 다양한 형태의 Controller를 실행하는 흐름을 보여준다.
Spring MVC의 요청 처리 흐름은 DispatcherServlet을 중심으로 움직인다.
| 순서 | 처리 단계 | 설명 |
|---|---|---|
| 1 | 요청 | 클라이언트 요청이 가장 먼저 DispatcherServlet에 도착한다. |
| 2 | HandlerMapping 조회 | 요청 URL에 맞는 Handler, 즉 Controller를 찾는다. |
| 3 | HandlerAdapter 실행 | 찾은 Controller를 실행할 수 있는 Adapter가 Controller 호출을 위임한다. |
| 4 | ModelAndView 반환 | Controller가 처리 결과 데이터와 View 이름을 반환한다. |
| 5 | ViewResolver 호출 | 논리적인 View 이름을 실제 View 파일로 변환한다. |
| 6 | View 렌더링 | View가 Model 데이터를 사용해 화면을 만든다. |
| 7 | 응답 | 완성된 결과가 클라이언트에게 전달된다. |
DispatcherServlet은 전체 흐름을 제어하고, HandlerMapping은 요청에 맞는 Controller를 찾으며, ModelAndView는 데이터와 View 이름을 함께 담는다. ViewResolver는 View 이름을 실제 View 파일로 바꾸고, View는 최종 HTML 화면을 렌더링한다.

Request가 DispatcherServlet에 도착한 뒤 HandlerMapping, Controller, ModelAndView, ViewResolver, View를 거쳐 Response로 이어지는 전체 흐름을 보여준다.
| 컴포넌트 | 한 줄 설명 |
|---|---|
| DispatcherServlet | Spring MVC의 Front Controller로, 전체 요청 처리 흐름을 조율한다. |
| HandlerMapping | 요청 URL과 매핑 정보를 기준으로 실행할 Controller를 찾는다. |
| HandlerAdapter | 다양한 Controller 형태를 통일된 방식으로 실행할 수 있게 해준다. |
| ModelAndView | Controller의 처리 결과 데이터와 View 이름을 함께 담는다. |
| ViewResolver | 논리적인 View 이름을 실제 View 파일 경로로 변환한다. |
| View | Model 데이터를 사용해 최종 화면을 만든다. |
이 표에서 가장 중요한 것은 각 컴포넌트를 따로 외우는 것이 아니라, 누가 누구에게 무엇을 요청하는지를 흐름으로 이해하는 것이다.
즉, Spring MVC는 Controller 하나만 동작하는 구조가 아니라 여러 컴포넌트가 역할을 나누어 요청과 응답을 처리하는 구조다.
실습에서는 HelloController와 hello.html을 만들어 /hello 요청이 화면 출력으로 이어지는 과정을 확인했다. HelloController는 @Controller로 등록하고, /hello 요청은 @GetMapping("/hello")로 맵핑했다.
HelloController에서 Model에 message 값을 담고 hello View를 반환하는 코드와, hello.html에서 th:text="${message}"로 값을 출력하는 코드를 보여준다.
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Hello, Spring!");
return "hello";
}
}
이 코드에서 핵심은 두 줄이다.
| 코드 | 의미 |
|---|---|
model.addAttribute("message", "Hello, Spring!") | Model에 message라는 key로 "Hello, Spring!" 값을 저장한다. |
return "hello" | hello라는 논리적인 View 이름을 반환한다. |
return "hello"는 단순히 문자열을 화면에 출력한다는 뜻이 아니다. @Controller에서는 이 문자열이 View 이름으로 해석된다. 이후 ViewResolver가 이 이름을 templates/hello.html과 연결한다. 자료의 /hello 예제도 HelloController에서 Model에 값을 담고, hello.html에서 해당 값을 출력하는 방식으로 구성된다.
<h1 th:text="${message}">(메시지가 보여지는 곳)</h1>
hello.html에서는 Thymeleaf 문법인 th:text="${message}"를 사용한다. 여기서 ${message}는 Controller가 Model에 저장한 "message" key를 의미한다. 따라서 View는 "message"라는 이름으로 값을 찾고, 그 결과 "Hello, Spring!" 문자열을 화면에 출력한다.
브라우저에서 localhost:8080/hello로 접속했을 때 Hello, Spring!이 출력된 결과를 보여준다.
브라우저에서 localhost:8080/hello에 접속하자 화면에 Hello, Spring!이 출력되었다. 이 결과는 단순히 HTML 파일이 열린 것이 아니라, 다음 흐름이 정상적으로 연결되었다는 의미다.
/hello 요청
→ DispatcherServlet
→ HandlerMapping
→ HelloController
→ Model에 message 저장
→ return "hello"
→ ViewResolver
→ hello.html
→ th:text="${message}"
→ Hello, Spring! 출력
콘솔에 Initializing Spring DispatcherServlet 'dispatcherServlet' 로그가 출력된 것도 의미가 있다. /hello 요청이 들어오면서 Spring MVC의 핵심 진입점인 DispatcherServlet이 요청 처리 흐름에 참여했다는 것을 확인할 수 있기 때문이다.
또 하나 주의할 점은 @Controller와 @RestController의 차이다. 이 실습은 View를 반환해야 하므로 @Controller를 사용해야 한다. 만약 @RestController를 사용하면 "hello"를 View 이름으로 해석하지 않고, 응답 본문에 문자열 "hello"를 그대로 내려줄 수 있다.
따라서 HTML 화면을 반환하는 흐름에서는 @Controller, JSON 데이터를 반환하는 API 흐름에서는 @RestController를 사용한다고 구분하면 된다
이번 정리의 핵심은 “Spring MVC가 많은 일을 대신 해준다”가 아니다. 더 정확히는 반복적이고 공통적인 요청 처리 흐름을 프레임워크가 구조화해주기 때문에 개발자가 핵심 로직에 더 집중할 수 있다는 것이다.
직접 작성해야 하는 부분은 Controller 메서드, Model에 담을 데이터, 반환할 View 이름 정도였다. 반면 DispatcherServlet, HandlerMapping, HandlerAdapter, ViewResolver 같은 내부 흐름은 Spring MVC가 담당했다. 이 구조 덕분에 개발자는 매 요청마다 Controller를 찾고, 실행하고, View를 연결하는 코드를 직접 작성하지 않아도 된다.
실무에서도 이 원리는 중요하다. 인증, 로깅, 인코딩, 공통 예외 처리처럼 여러 요청에 반복되는 작업은 중앙화할수록 유지보수가 쉬워진다. 또한 다양한 Controller 형태를 Adapter로 처리할 수 있으면 기존 코드를 크게 수정하지 않고도 구조를 확장할 수 있다. 결국 Front Controller Pattern과 Adapter Pattern은 단순한 이론이 아니라, 큰 애플리케이션에서 요청 처리 흐름을 일관되게 유지하기 위한 구조적 장치다.
Servlet 방식은 간단한 API를 만들 때는 충분히 동작하지만, 기능이 많아질수록 요청 처리, 비즈니스 로직, 응답 생성 코드가 한곳에 섞이는 문제가 생긴다. Spring MVC는 이 문제를 Controller, Model, View로 나누어 해결하고, DispatcherServlet을 중심으로 요청 처리 흐름을 구조화한다.
가장 중요한 깨달음은 사용자의 요청이 Controller로 바로 가는 것이 아니라는 점이다. 요청은 먼저 DispatcherServlet에 도착하고, HandlerMapping과 HandlerAdapter를 거쳐 적절한 Controller로 전달된다. Controller는 처리 결과를 Model에 담고 View 이름을 반환하며, ViewResolver와 View가 최종 화면을 만들어 응답한다.
/hello 실습에서는 Model에 "message"라는 key로 "Hello, Spring!" 값을 저장하고, hello.html에서 ${message}를 읽어 화면에 출력하는 과정을 확인했다. 이 결과를 통해 Controller → Model → View 흐름이 실제로 어떻게 연결되는지 확인할 수 있었다.
Spring MVC의 핵심은 개발자가 모든 요청 처리 과정을 직접 구현하지 않아도 되도록 구조를 제공하는 것이다. 그래서 개발자는 반복적인 흐름보다 실제 기능 구현과 핵심 애플리케이션 로직에 더 집중할 수 있다.