개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Spring › 웹 MVC

MVC 요청 흐름

요청 하나가 컨트롤러에 닿기까지, 그리고 돌아가기까지

MVC는 프로그램을 모델(Model, 데이터), 뷰(View, 화면), 컨트롤러(Controller, 요청을 받아 처리하는 곳)로 나누는 방식이고, 스프링 MVC는 이 방식으로 웹 요청을 처리합니다. HTTP 요청은 필터, DispatcherServlet, 인터셉터를 차례로 거쳐 컨트롤러에 도착하고, 같은 길을 거꾸로 돌아 응답이 됩니다. 요청 하나가 지나가는 길에서 컨트롤러, 모델, 뷰가 각각 몇 번째 단계에서 일하는지, JSON을 돌려주는 @RestController에서는 그중 무엇이 빠지는지 봅니다.

실행으로 대조 · 스프링 부트 4.1.1 서버에 열여섯 가지 요청을 보내 대조

이 실습에 나오는 개념

MVC(Model-View-Controller)
프로그램을 모델(Model), 뷰(View), 컨트롤러(Controller) 세 역할로 나누는 설계 방식이다. 모델은 데이터와 그 데이터를 다루는 로직을 맡고, 뷰는 데이터를 화면으로 보여 주는 일을 맡으며, 컨트롤러는 들어온 입력을 받아 모델과 뷰를 움직인다. 스프링 MVC는 이 방식을 따르는 웹 프레임워크다. DispatcherServlet이 모든 요청을 받아 알맞은 컨트롤러 메서드를 부르고, 컨트롤러가 화면에 보여 줄 데이터를 모델에 담아 뷰 이름과 함께 돌려주면, 뷰가 그 데이터로 응답 화면을 만든다. JSON을 응답하는 REST API에서는 뷰가 화면을 그리는 대신 HttpMessageConverter가 컨트롤러의 반환값을 응답 본문으로 바꾼다.
DispatcherServlet과 프론트 컨트롤러
Spring MVC에서 모든 HTTP 요청을 가장 먼저 받는 서블릿이다. 요청을 처리할 컨트롤러를 찾아 호출하고 그 결과를 응답으로 만드는 전체 흐름을 조율한다. 이렇게 하나의 진입점이 모든 요청을 받아 공통 처리를 한 뒤 각 담당에게 넘기는 구조를 프론트 컨트롤러 패턴이라 한다.
HandlerMapping과 HandlerAdapter
DispatcherServlet이 요청을 처리할 때 쓰는 구성 요소다. HandlerMapping은 요청 URL과 메서드에 맞는 핸들러(컨트롤러 메서드)를 찾는다. HandlerAdapter는 찾은 핸들러를 실제로 실행하며, 요청 데이터를 메서드의 매개변수에 맞게 변환해 넣고 반환값을 처리한다.
ArgumentResolver
컨트롤러 메서드의 매개변수에 넣을 값을 요청에서 꺼내 만들어 주는 구성 요소다. @RequestParam, @PathVariable, @RequestBody 등이 붙은 매개변수가 이를 통해 채워진다. 직접 구현하면 로그인한 사용자 정보처럼 반복되는 값을 컨트롤러 매개변수로 바로 받을 수 있다.
HttpMessageConverter
HTTP 요청 본문을 자바 객체로, 자바 객체를 HTTP 응답 본문으로 변환하는 구성 요소다. @RequestBody와 @ResponseBody가 이를 사용한다. JSON의 경우 Jackson 라이브러리를 이용한 컨버터가 변환을 맡는다.
필터(Filter)
서블릿 컨테이너 수준에서 요청과 응답을 가로채 처리하는 구성 요소로 서블릿 표준에 속한다. 요청이 DispatcherServlet에 도달하기 전과 응답이 돌아가는 길에 동작하며, 인코딩 설정, CORS 처리, 인증 같은 모든 요청에 걸친 작업에 쓴다. Spring Security도 필터들의 묶음으로 동작한다.
인터셉터(HandlerInterceptor)
Spring MVC 안에서 컨트롤러 실행 전후에 동작하는 구성 요소다. 컨트롤러 실행 전(preHandle), 실행 후(postHandle), 요청 처리 완료 후(afterCompletion)의 세 시점에 끼어들 수 있고, 어떤 컨트롤러 메서드가 실행될지 알 수 있다. 필터가 서블릿 컨테이너의 기능이라면 인터셉터는 스프링의 기능이다.
@ControllerAdvice와 @ExceptionHandler
여러 컨트롤러에서 발생한 예외를 한곳에서 처리하게 하는 기능이다. @ControllerAdvice 클래스 안에 예외 종류별로 @ExceptionHandler 메서드를 두면, 컨트롤러 실행 중 발생한 예외가 그 메서드로 전달되어 일관된 형식의 오류 응답을 만들 수 있다.
ViewResolver
컨트롤러가 반환한 뷰 이름(문자열)을 실제 화면을 그릴 뷰 객체로 바꿔 주는 구성 요소다. 서버에서 HTML을 만들어 응답하는 방식에서 사용하며, JSON을 직접 응답하는 REST API에서는 사용되지 않는다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. GET /orders 요청이 서블릿 컨테이너(톰캣)에 도착해 필터 체인을 탑니다. 필터는 스프링 MVC 바깥, 서블릿 수준에서 동작합니다. 인코딩, 인증, 로깅 필터가 차례로 chain.doFilter를 호출해 다음으로 넘깁니다.
  2. DispatcherServlet이 요청을 받습니다. 모든 요청이 이 서블릿 하나로 들어오고, 여기서 알맞은 컨트롤러로 나눠 보냅니다(프론트 컨트롤러).
  3. HandlerMapping이 GET /orders를 처리할 핸들러를 찾습니다. @GetMapping("/orders")가 붙은 list 메서드와, 이 경로에 적용할 인터셉터 목록을 묶어 돌려줍니다.
  4. HandlerAdapter를 고릅니다. 애너테이션 기반 컨트롤러 메서드를 실행할 줄 아는 RequestMappingHandlerAdapter가 선택됩니다.
  5. 인터셉터의 preHandle이 실행되고 true를 반환합니다. 필터와 달리 어떤 컨트롤러 메서드가 실행될지 아는 상태에서 동작합니다.
  6. ArgumentResolver가 컨트롤러 메서드의 파라미터를 준비합니다. Model 타입 파라미터에는 화면에 넘길 데이터를 담을 빈 Model 객체를 넣어 줍니다. 여기에 담기는 데이터가 MVC의 모델(M)입니다.
  7. 컨트롤러 메서드가 실행됩니다. MVC의 컨트롤러(C)가 일하는 단계입니다. 서비스 계층을 호출해 주문 목록을 가져오고, 그것을 모델에 orders라는 이름으로 담은 뒤 화면 이름 "orders/list"를 돌려줍니다.
  8. 반환값을 처리합니다. @Controller가 돌려준 문자열 "orders/list"는 화면 이름으로 해석되고, 모델과 함께 ModelAndView로 묶입니다.
  9. 인터셉터의 postHandle이 호출됩니다. 아직 화면을 그리기 전이라 모델에 공통 데이터를 추가할 수 있습니다.
  10. ViewResolver가 화면 이름 "orders/list"를 실제 템플릿 파일로 바꾸고, 뷰가 모델 데이터를 넣어 HTML을 그려 응답에 씁니다. MVC의 뷰(V)가 일하는 단계이고, 컨트롤러가 모델에 담아 둔 orders를 여기서 꺼내 씁니다.
  11. 인터셉터의 afterCompletion이 호출됩니다. 응답 작성이 끝난 뒤이며 예외 여부와 관계없이 호출됩니다.
  12. 응답이 필터 체인을 거꾸로 지나 클라이언트로 나갑니다. chain.doFilter 호출 뒤에 적어 둔 필터 코드가 이때 실행됩니다.

실행 결과

코드

@Controller
class OrderController {
    @GetMapping("/orders")
    String list(Model model) {
        model.addAttribute("orders", orderService.findAll());
        return "orders/list";              // 화면 이름
    }
}

이 모형의 전제

인터셉터와 필터가 각각 하나씩 있는 단순한 구성으로 그렸습니다. 열여섯 가지 조합 모두를 실제 스프링 부트 4.1.1 서버(내장 톰캣)에 HTTP 요청을 보내, 응답 코드와 컨트롤러·postHandle·afterCompletion의 실행 여부, 응답을 만든 곳이 화면과 일치하는 것을 확인했습니다(화면 응답은 템플릿 엔진 대신 같은 이름의 뷰 객체로 그려서 확인). 처리되지 않은 예외가 /error로 다시 전달되어 500 응답이 되는 부분은 스프링 부트의 기본 오류 처리 동작이며, 그 재전달 과정은 한 단계로 줄여서 표현했습니다. 매핑되는 컨트롤러가 없는 경우(404)는 버전과 정적 리소스 설정에 따라 경로가 달라서 넣지 않았습니다.

앞 실습: 서블릿 · 다음 실습: 요청 매핑과 파라미터 바인딩

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요