회사에서 개발을 진행하며 초기에는 Spring이였지만, Spring boot로 환경이 바뀌게 되었다. Spring Boot로 프로젝트를 하다 보면 흔히 "톰캣 위에서 돌아가는 구조"라고만 알고 넘어가는 경우가 많은데..
하지만 막상 DispatcherServlet이 뭔지, Spring Container가 어디서 실행되는지, 그리고 톰캣은 실제로 어떤 역할을 하는지까지 명확하게 이해하지 못한 채 개발을 진행하는 경우도 많다. 내가 그랬던거 같다
나는 실제 업무에서 Spring MVC에서의 URL 매핑 문제를 디버깅하다가 RequestMappingHandlerMapping을 타고 들어갔고, 그 흐름이 결국 DispatcherServlet, 나아가 ServletContainer와 Spring Container로 이어진다는 사실을 알게 되었다. 단순히 "컨트롤러가 매핑이 안 되네?"가 아니라, 컨트롤러 호출의 전제 조건이 되는 전체 구조를 제대로 이해해야 디버깅도, 확장도 정확히 할 수 있다는 걸 알게 되었다.
이 글에서는 Web Server, Servlet Container, Spring Container의 개념을 분리해 이해하고, Spring MVC가 요청을 어떻게 처리하는지에 대한 흐름을 실제 예시와 함께 정리해보았다.
| 구성 요소 | 역할 | 예시 |
|---|---|---|
| Web Server | HTTP 요청 수신, 정적 리소스 제공 | Nginx, Apache, 톰캣 (HTTP 커넥터 기능 포함) |
| Servlet Container | 서블릿 생명주기 관리 및 실행 (HttpServlet) | 톰캣, Jetty, Undertow |
| Spring Container | Bean 생성, 의존성 주입, AOP, 트랜잭션 관리 | ApplicationContext |
💡 톰캣은 Web Server + Servlet Container 기능을 모두 포함한 경량 WAS이다.
Spring Boot + 내장 톰캣 구조를 사용하는 경우가 많다. 이 경우 Web Server와 Servlet Container, Spring Container가 한 프로세스 안에 존재한다.Nginx (정적 리소스 + 리버스 프록시) + Tomcat (서블릿 컨테이너) + Spring 구조로 분리하는 경우가 많다.# Nginx 예시 설정 (리버스 프록시)
location /api/ {
proxy_pass http://localhost:8080/;
}
[Web Browser]
↓ HTTP 요청
[Web Server] ← ex) Nginx 또는 Tomcat HTTP 커넥터
↓
[Servlet Container]
└─ DispatcherServlet (Spring이 등록한 HttpServlet)
↓
[Spring Container (ApplicationContext)]
└─ Controller, Service, Repository Bean 실행
이 중 DispatcherServlet이 핵심이다. Spring MVC에서는 이 서블릿이 프론트 컨트롤러로서 모든 요청을 가로채고, 이후 컨트롤러를 실행하는 전 과정(HandlerMapping → HandlerAdapter → ViewResolver)을 지휘한다.
Spring Boot는 DispatcherServlet을 자동으로 등록하지만, 전통적인 방식에서는 다음과 같이 수동 등록할 수도 있다.
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(MyWebConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
Spring Container는 DispatcherServlet이 초기화될 때 함께 생성된다.
WebApplicationContext를 초기화public class DispatcherServlet extends FrameworkServlet {
@Override
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
// 내부에서 ApplicationContext 생성 및 초기화
}
}
Spring은 독립된 어플리케이션이 아니라, 서블릿 환경에서 실행되는 프레임워크이다.
Servlet Container가 없다면 DispatcherServlet도 존재할 수 없고, Spring MVC도 작동하지 않는다.
| 계층 | 역할 |
|---|---|
| Web Server | HTTP 수신, 정적 리소스 제공, 리버스 프록시 |
| Servlet Container | HttpServlet을 실행, 요청을 서블릿에 위임 |
| DispatcherServlet | Spring MVC의 프론트 컨트롤러 역할 수행 |
| Spring Container | Bean 관리, AOP, 트랜잭션, 서비스 계층 실행 |
Spring MVC 구조를 겉으로 보면 단순한 것 같지만, 실제로 DispatcherServlet이 서블릿 위에 올라가 있다는 구조를 이해하지 못하면 디버깅이나 확장 작업에서 큰 어려움을 겪게 된다.
특히 "서블릿 컨테이너 위에 DispatcherServlet이 있고 그 안에서 Spring Container가 실행된다"는 개념은 실무에서 가장 많이 놓치는 부분 중 하나일 것 같다..
이번 공부를 하기전에는 흐름을 어렴풋이만 알고 있었지만 이제는 명확하게 알게 해주는, 좋은 공부였던 것 같다.
다음 편에서는 HandlerMapping, HandlerAdapter, ViewResolver 같은 구성요소에 대해서 더 깊게 공부해볼 예정이다.