Spring MVC와 Servlet

Computer Science/Spring Boot

서버에서 수행해야 하는 일

서버는 사용자의 HTTP 요청을 받고 해당 요청에 따른 응답을 내려준다. 하지만 우리가 코드를 작성할 땐 HTTP 요청을 받기 위한 TCP소켓을 열고, HTTP 메세지를 파싱하고 응답 메세지를 작성하고 하는 번거로운 일을 하지 않고 "어떤 요청에서는 어떤 로직이 수행된다"라는 코드만 작성한다.

어떻게 이렇게 간단하게 할 수 있을까?

그 이유는 해당 작업들을 WAS가 수행해주기 때문이다. 흔히 사용하는 Tomcat에서는 서블릿 컨테이너를 통해 서블릿 스펙을 지원한다. Tomcat이 아니더라도 다른 이전 포스팅에서 말했던 다른 서블릿 컨테이너들은 모두 서블릿 스펙을 지원한다. 그럼 서블릿 스펙을 지원하지 않는 다른 WAS들은 HTTP 파싱부터 다 해야하나요? 라는 생각이 들 수도 있지만 우리가 작성해야하는 코드가 달라질 뿐 해당 기능들을 지원한다. 대표적으로 서블릿을 구현하지 않는 Netty는 서블릿 기반인 Spring MVC가 아닌 Spring WebFlux라는 프레임워크를 사용해야한다. 서블릿을 지원하진 않지만 별도의 추상화를 지원하기 때문에 크게 걱정하진 않아도 된다.

 

Spring MVC와 Servlet

이 글에서는 자주 사용하는 Tomcat과 Tomcat이 지원하는 서블릿 컨테이너 그리고 서블릿에 대해서 정리할 예정이다.

먼저 서블릿이란 자바 기반으로 HTTP 요청/응답을 처리하는 표준 인터페이스(명세)이다.

 

우리가 프로젝트에서 자주 사용하는 Spring MVC는 서블릿을 바탕으로 구현된 프레임워크이다. 웹 애플리케이션을 MVC(Model-View-Controller) 기반으로 쉽게 개발할 수 있도록 도와준다. 서블릿 API 위에서 요청-응답 흐름을 DispatcherServlet이 관리한다.

 

먼저 MVC에 대해서 간략하게 알아보자.

MVC 패턴

MVC는 소프트웨어 설계 패턴 중 하나로, Model-View-Controller의 약자이고 복잡한 애플리케이션을 역할별로 나누어 관리하기 쉽게 만든 아키텍쳐 패턴이다.

 

  • Model (모델)
    • 애플리케이션의 데이터와 비즈니스 로직을 담당.
    • 예: DB에서 데이터를 가져오는 객체, 도메인 로직을 수행하는 서비스 클래스.
    • “무엇을 보여줄지”에 해당하는 내용.
  • View (뷰)
    • 사용자가 보는 UI를 담당.
    • Model의 데이터를 사용자에게 표현하는 역할.
    • 웹 개발에서는 HTML, JSP, Thymeleaf, React 화면 등이 뷰에 해당.
  • Controller (컨트롤러)
    • 사용자의 요청을 받고(Model과 View를 연결) 응답을 반환.
    • 사용자의 입력을 처리하고, 그 결과를 Model에서 가져와 View로 전달.
    • 웹 개발에서는 @Controller 클래스, @RestController 메서드 등이 이에 해당.

 

이렇게 정의가 되어있는데 Spring MVC는 해당 패턴으로 개발하기 쉽게 도와준다.

Spring MVC 구조
starter-web 의존성에 포함되어있는 Spring MVC

이전에 진행했던 프로젝트의 gradle인데 starter-web의존성에 tomcat과 mvc가 의존성으로 들어가있다.

 

그럼 Spring MVC는 위 그림과 같이 요청을 처리하는데 해당 그림만 보고는 이해하기 어렵다. 상세하게 풀어서 설명해보자면 다음과 같다.

1) 서블릿 컨테이너 진입

  • Tomcat(혹은 다른 서블릿 컨테이너)가 TCP 연결을 수락하고 워커 스레드 1개를 요청에 배정
  • Spring MVC는 동기+블로킹 모델이므로 이 스레드는 보통 요청이 끝날 때까지 해당 요청에 고정된다.

2) 필터 체인(Filter)

  • Servlet Filter 들이 순서대로 실행
    • 예: 로깅 필터, GZip, CORS, 캐시 제어, Spring Security Filter Chain 등
  • doFilter()에서 다음 체인으로 넘길지/차단할지 결정.

3) DispatcherServlet(핵심 진입점)

  • 모든 URL을 /*로 매핑해 둔 DispatcherServlet 이 요청을 받음
  • 위 그림처럼 여기서부터 Spring MVC로 진입

4) 핸들러 탐색 & 어댑터

  • HandlerMapping: 이 요청을 처리할 컨트롤러 메서드(또는 핸들러 함수)를 찾음
    • 예: @RequestMapping, @GetMapping 등으로 등록된 메서드
  • HandlerAdapter: 찾은 핸들러를 어떻게 호출(실행)할지 결정
    • Spring MVC는 여러 가지 핸들러(컨트롤러) 형태를 지원하기 때문에 해당 핸들러를 어떻게 실행할지 아는 어댑터가 필요
    • 만약 핸들러 어댑터가 없다면 새로운 핸들러가 추가될 때마다, 혹은 핸들러의 처리 방식이 변경될 때마다 DispatcherServlet을 수정해야하기 때문에 OCP위배

5) 인터셉터(선/후 처리 훅)

  • HandlerInterceptor의 preHandle() 실행
    • 인증·권한·공통 컨텍스트 주입·A/B 플래그 결정 등
  • 컨트롤러 실행 후에는 postHandle()/afterCompletion()이 호출

6) 인자 해석 & 바인딩 & 검증

  • HandlerMethodArgumentResolver 가 컨트롤러 파라미터를 채움
    • @PathVariable, @RequestParam, @RequestHeader, @RequestBody 등
    • @RequestBody → HttpMessageConverter가 JSON 등을 객체로 역직렬화
  • DataBinder가 폼 바인딩 처리, @Valid + Validator로 검증, BindingResult에 에러 저장

7) 컨트롤러 실행 (Service/Repository로 위임)

  • 컨트롤러가 Service 호출 → Repository(JPA/JDBC) 접근
  • Spring MVC + JDBC/JPA는 보통 블로킹 I/O이므로, 현재 워커 스레드가 DB/외부 API 응답을 기다립니다.
  • @Transactional 로 트랜잭션 경계 관리(AOP 프록시)

8) 리턴값 처리: 뷰 vs 바디

  • 컨트롤러 리턴값에 따라 나뉜다.
    1. 뷰 렌더링: 문자열 뷰이름 반환 → ViewResolver가 템플릿(Thymeleaf/JSP 등) 찾아 렌더
    2. 본문(JSON 등): 객체/ResponseEntity 반환 → HttpMessageConverter로 직렬화 후 HTTP 본문에 씀

9) 예외 처리

  • 실행 중 예외 발생 시 HandlerExceptionResolver / @ControllerAdvice 의 @ExceptionHandler 가 잡아서
    • 에러 응답(JSON) 또는 에러 뷰로 변환

10) 인터셉터 마무리 & 필터 복귀

  • postHandle() → 응답 후처리
  • 응답 커밋 후 afterCompletion() 로 정리(리소스 해제, 로깅)

11) 응답 송신 & 연결 유지

  • 서블릿 컨테이너가 HTTP 응답 헤더/바디 전송
  • Keep-Alive라면 연결을 풀에 유지, 아니면 종료
  • 액세스 로그, 메트릭, 트레이싱(span) 기록

사실 위의 그림은 View가 Thymeleaf와 같이 html을 렌더링하는 기준으로 되어있다. 하지만 요즘은 별도의 프론트엔드 서버를 두기 때문에 MVC 패턴의 View가 JSON과 같은 HTTP의 본문만 반환한다고 생각하면 된다.

 

서블릿 동작을 좀 더 딥하게 들어가보자면 아래의 흐름과 같다.

기본 서블릿 동작

  • 모든 서블릿은 HttpServlet을 상속
  • 클라이언트가 요청을 보내면 서블릿 컨테이너(Tomcat 등) 가 HttpServlet.service() 메서드를 호출 (인스턴스는 DispatcherServlet)
  • service()는 요청 메서드(GET, POST 등)에 따라 doGet(), doPost() 같은 메서드를 실행

DispatcherServlet의 흐름

  • DispatcherServlet은 FrameworkServlet을 상속
  • FrameworkServlet은 HttpServlet.service()를 오버라이드해서, 결국 doService()를 호출하도록 구현
  • 그리고 DispatcherServlet은 doService()를 오버라이드해서 내부에서 doDispatch()를 실행

또한 DispatcherServlet은 싱글톤 객체로 관리된다.

왜 싱글톤으로 관리될까?

  • 성능: 매 요청마다 서블릿 인스턴스를 새로 만들면 오버헤드가 큼
  • 안정성: 공용 설정/리소스를 하나의 객체가 유지할 수 있음
  • 그래서 컨테이너는 애플리케이션 시작 시 서블릿 객체를 단 한 번 생성하고, 이후 들어오는 모든 요청을 같은 서블릿 인스턴스의 service() 메서드에 위임

주의할 점

  • 서블릿 객체는 싱글톤이지만, HttpServletRequest / HttpServletResponse 같은 요청·응답 객체는 매 요청마다 새로 만들어짐
    • 서블릿 인스턴스(예: DispatcherServlet)는 공용
    • 요청/응답 객체는 요청마다 다른 인스턴스

그래서 따로 건들일은 없겠지만 멀티스레드 환경에서 여러 요청이 동시에 같은 서블릿 인스턴스를 공유하기 때문에 서블릿 클래스 안에 상태를 저장하는 필드(인스턴스 변수)를 두면 위험하다.

 

여러 요청이 오면 싱글톤인데 어떡하나요? 라는 질문이 생길 수 있는데, 그 답변은 여러 스레드가 하나의 DispatcherServlet을 통해 요청마다 service() -> doDispatch() 메서드가 실행된다. 따라서 DispatcherServlet은 요청에 따른 메서드만 호출할 뿐이다.

 

참고자료

https://isaac-christian.tistory.com/entry/Spring-Spring-MVC-Framework-MVC-%ED%8C%A8%ED%84%B4%EC%9D%98-%EA%B5%AC%EC%A1%B0-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95

 

[Spring] Spring MVC Framework: MVC 패턴의 구조, 동작 과정

Spring MVC Framework는 웹 개발에서 널리 사용되는 아키텍처 중 하나로, MVC(Model-View-Controller) 패턴을 기반으로 한다. 💡MVC 패턴의 구조 Model 정의: 애플리케이션의 데이터를 책임지며, 비즈니스 로직

isaac-christian.tistory.com

 

'Computer Science > Spring Boot' 카테고리의 다른 글

N+1 해결을 통한 성능 개선기  (0) 2025.09.02
JPA N+1 문제  (0) 2025.09.02
스프링 컨테이너와 빈(Bean) - Lifecycle Callback과 Scope  (0) 2025.08.24
스프링 컨테이너와 빈(Bean) - 싱글톤과 의존관계 주입 방법  (0) 2025.08.21
스프링 컨테이너와 빈(Bean) - ApplicationContext 동작 과정  (0) 2025.08.17
'Computer Science/Spring Boot' 카테고리의 다른 글
  • N+1 해결을 통한 성능 개선기
  • JPA N+1 문제
  • 스프링 컨테이너와 빈(Bean) - Lifecycle Callback과 Scope
  • 스프링 컨테이너와 빈(Bean) - 싱글톤과 의존관계 주입 방법
hojoo
hojoo
그냥 개발이 즐거운 사람
  • hojoo
    dev_record
    hojoo
  • 전체
    오늘
    어제
    • 분류 전체보기 (84)
      • Study (0)
        • 모든 개발자를 위한 HTTP 웹 기본 지식 (0)
        • Real MySQL 8.0 (0)
        • 친절한 SQL 튜닝 (0)
        • 도메인 주도 개발 시작하기 (0)
        • 대규모 시스템 설계 기초 (0)
      • Computer Science (68)
        • Problem Solving (30)
        • Data Structure (4)
        • Spring Boot (14)
        • DB (1)
        • Java (4)
        • OS (3)
        • Server (3)
        • Tech (0)
      • Security (16)
        • Reversing (15)
        • Assembly (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    리버싱 핵심원리
    16946
    Lena tutorial
    2539
    레나 튜토리얼
    프로그래머스
    dreamhack.io
    bean
    x64dbg
    9421
    PE header
    HTTP
    소수상근수
    13265
    n^2 배열 자르기
    Spring boot
    DB
    servlet
    리버싱
    Reversing
    15973
    21278
    자료구조
    백준
    19622
    12033
    서버 증설 횟수
    n+1
    Header
    DP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
hojoo
Spring MVC와 Servlet
상단으로

티스토리툴바