Spring MVC의 요청 처리 흐름(DispatcherServlet 동작 방식)을 설명해주세요.
힌트
DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ViewResolver 순서입니다.
정답 및 해설
Spring MVC의 요청 처리 흐름(DispatcherServlet 동작 방식)을 설명해주세요.
Spring MVC는 Front Controller 패턴을 기반으로 동작합니다. 모든 HTTP 요청이 하나의 진입점인 DispatcherServlet을 통해 처리되며, 이후 각 역할에 특화된 컴포넌트들이 협력하여 응답을 생성합니다.
전체 요청 처리 흐름
클라이언트
│
▼
DispatcherServlet (Front Controller)
│
├─ 1. HandlerMapping → 적절한 Handler(Controller) 탐색
│
├─ 2. HandlerAdapter → Handler 실행
│
├─ 3. Controller → 비즈니스 로직 처리 → ModelAndView 반환
│
├─ 4. ViewResolver → 논리 뷰 이름 → 실제 View 변환
│
└─ 5. View → 렌더링 → 응답 반환
단계별 상세 설명
1단계: 클라이언트 요청 수신
클라이언트의 HTTP 요청이 서블릿 컨테이너(Tomcat)를 거쳐 DispatcherServlet에 도달합니다. web.xml 또는 Spring Boot의 자동 설정으로 모든 요청(/)이 DispatcherServlet에 매핑됩니다.
// Spring Boot에서는 자동으로 설정됨
// 직접 설정이 필요한 경우:
@Bean
public ServletRegistrationBean<DispatcherServlet> dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet(applicationContext);
return new ServletRegistrationBean<>(servlet, "/");
}
2단계: HandlerMapping으로 Handler 탐색
DispatcherServlet이 요청 URL과 HTTP 메서드에 맞는 핸들러(Controller)를 HandlerMapping에 질의합니다.
// 내부적으로 이런 과정이 일어납니다:
// GET /users/1 → UserController.getUser(Long id) 메서드 탐색
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
// HandlerMapping이 이 메서드를 찾아냄
return userService.findById(id);
}
}
Spring MVC에는 여러 HandlerMapping 구현체가 있으며, RequestMappingHandlerMapping이 가장 일반적으로 사용됩니다.
3단계: HandlerAdapter로 Handler 실행
핸들러의 타입에 따라 적절한 HandlerAdapter가 선택되어 실제 메서드를 호출합니다. HandlerAdapter는 다양한 형태의 핸들러(Controller, HttpRequestHandler 등)를 일관된 방식으로 실행할 수 있게 해줍니다.
// RequestMappingHandlerAdapter가 아래와 같은 작업을 처리합니다:
// 1. @RequestParam, @PathVariable, @RequestBody 등 파라미터 바인딩
// 2. 메서드 실행
// 3. 반환값 처리 (ModelAndView, String, ResponseEntity 등)
4단계: Controller 비즈니스 로직 처리
Controller는 Service를 호출해 비즈니스 로직을 수행하고 결과를 반환합니다.
@Controller // @RestController가 아닌 경우
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
@GetMapping("/{id}")
public String getOrder(@PathVariable Long id, Model model) {
Order order = orderService.findById(id);
model.addAttribute("order", order);
return "order/detail"; // 논리 뷰 이름 반환
}
}
5단계: ViewResolver로 View 결정
Controller가 반환한 논리 뷰 이름(예: "order/detail")을 실제 View 객체로 변환합니다.
// application.yml 설정
// spring:
// mvc:
// view:
// prefix: /WEB-INF/views/
// suffix: .jsp
// "order/detail" → /WEB-INF/views/order/detail.jsp
| ViewResolver 종류 | 설명 |
|---|---|
InternalResourceViewResolver | JSP 파일로 변환 |
ThymeleafViewResolver | Thymeleaf 템플릿으로 변환 |
BeanNameViewResolver | 빈 이름으로 View 탐색 |
6단계: View 렌더링 및 응답 반환
View가 Model 데이터를 사용해 HTML 등의 응답을 생성하고 클라이언트에 반환합니다.
@RestController의 경우: HttpMessageConverter
@RestController(또는 @ResponseBody)를 사용하면 ViewResolver 대신 HttpMessageConverter가 반환값을 직렬화합니다.
클라이언트
│
▼
DispatcherServlet
│
├─ HandlerMapping → Controller 탐색
├─ HandlerAdapter → Controller 실행
│
└─ @ResponseBody 감지
│
▼
HttpMessageConverter (Jackson 등)
│ Java 객체 → JSON/XML 직렬화
▼
HTTP Response Body
@RestController // @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserApiController {
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
// UserResponse 객체가 Jackson에 의해 JSON으로 직렬화됨
return userService.findById(id);
}
@PostMapping
public ResponseEntity<UserResponse> createUser(@RequestBody CreateUserRequest request) {
UserResponse response = userService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(response);
}
}
주요 HttpMessageConverter 구현체:
| Converter | 처리 타입 |
|---|---|
MappingJackson2HttpMessageConverter | JSON (application/json) |
StringHttpMessageConverter | 문자열 (text/plain) |
ByteArrayHttpMessageConverter | 바이트 배열 (application/octet-stream) |
MarshallingHttpMessageConverter | XML (application/xml) |
HandlerInterceptor와의 관계
HandlerInterceptor는 DispatcherServlet 이후, Controller 실행 전후에 동작합니다.
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// Controller 실행 전 — false 반환 시 요청 중단
String token = request.getHeader("Authorization");
if (!tokenService.isValid(token)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// Controller 실행 후, View 렌더링 전
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// View 렌더링 후 (예외 발생 여부 무관)
// 리소스 정리에 사용
}
}
전체 컴포넌트 역할 요약
| 컴포넌트 | 역할 |
|---|---|
DispatcherServlet | 요청을 받아 전체 흐름을 조율하는 Front Controller |
HandlerMapping | URL과 HTTP 메서드로 적절한 Handler 탐색 |
HandlerAdapter | 다양한 형태의 Handler를 일관되게 실행 |
Controller | 실제 비즈니스 로직 처리 및 결과 반환 |
ViewResolver | 논리 뷰 이름을 실제 View로 변환 |
View | Model 데이터를 사용해 최종 응답 생성 |
HttpMessageConverter | @ResponseBody 사용 시 객체를 JSON/XML로 직렬화 |