Dispatch는 보내다라는 뜻을 가지고 있습니다. 그러면 “Dispatch-Servlet”은 서블릿을 보내다는 것을 단어에서 알 수 있습니다.
디스패처 서블릿은 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러라고 정의할 수 있습니다.
위의 그림에서도 나타나듯이 가장 먼저 “Request”를 받아서 처리하는 것을 확인할 수 있습니다.
그 다음 디스패처 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야 하는 컨트롤러를 찾아서 작업을 위임합니다.
Spring MVC는 Dispatcher-Servlet이 등장함에 따라 web.xml의 역할을 상당히 축소시켰습니다. 이전에는 모든 서블릿을 매핑하기 위해 web.xml에 모두 등록해주어야 했지만, Dispatcher-Servlet이 해당 애플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리하면서 상당히 편리하게 이용할 수 있게 되었습니다.
개발자는 컨트롤러를 구현해두기만 하면 Dispatcher-Servlet이 알아서 적합한 컨트롤러로 위임을 해주는 구조가 되었습니다.
<web-app>
<servlet>
<servlet-name>firstServletName</servlet-name>
<servlet-class>com.serlvet.FirstServet</servlet-class>
</servlet>
<servlet>
<servlet-name>secondServletName</servlet-name>
<servlet-class>com.serlvet.SecondServet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstServletName</servlet-name>
<url-parttern>/first</url-parttern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>secondServletName</servlet-name>
<url-parttern>/second</url-parttern>
</servlet-mapping>
</web-app>
다음과 같이 작성해야 하는 번거로운 작업이 줄어들게 되었습니다.
기존에 web.xml에 번거롭게 작성해야하는 작업이 Dispatcher-Servlet이 등장하여 편리하게 사용 가능
Dispatcher-Servlet이 요청을 컨트롤러로 넘겨주는 방식은 효율적으로 보이지만, 모든 요청을 처리하다보니 이미지나 HTML/CSS/Javascript 등과 같은 정적 파일에 대한 요청마저 모두 가로채는 까닭에 정적 자원을 불러오지 못하는 상황이 발생하였습니다.
이러한 문제를 해결하기 위해 2가지 방법이 고안되었습니다.
클라이언트의 요청을 2가지로 분리하여 구분하는 것
/apps 의 URL로 접근하면 Dispatcher-Servlet이 담당한다./resources 의 URL로 접근하면 Dispatcher-Servlet이 컨트롤할 수 없으므로 담당하지 않는다.진행중인 프로젝트에서
Swagger의 정적 자원 요청을 위한 설정을 분리하여 작성하는 것을 확인할 수 있습니다. 이렇게 되면/static의 URL로 접근하면 Dispatcher-Servlet이 컨트롤하지 않습니다.@Configuration public class SwaggerConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/static/swagger-ui/"); } }
디스패처 서블릿이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원 경로를 탐색하여 자원을 탐색합니다.
Dispatcher-Servlet은 적합한 컨트롤러와 메서드를 찾아 요청을 위임해야 합니다. 먼저 Spring MVC 의 라이프 사이클을 확인해보면 다음과 같습니다.
해당 포스트에서는 Dispatcher-Servlet을 중심적으로 다루기 때문에 “Dispatcher-Servlet을 통해 요청을 처리할 컨트롤러를 찾아서 위임하고, 그 결과를 받아온다.”로 이해해도 좋습니다.
글로만 보면 이해하기 힘드니 예제 코드와 함께 확인해보도록 하겠습니다.
Amazon Corretto Java 17.0.12
Spring Boot 3.3.3
Spring Boot Starter Web
Lombok
InteillJ IDEA
Httpie
앞에서도 설명한것 처럼 Dispatcher-Servlet은 가장 먼저 요청을 받는 곳 입니다. 아래 그림은 처리 순서를 도직화한 것으로만 이해하면 됩니다.

이미지 출처: https://mangkyu.tistory.com/18
httpie를 사용하여 요청을 하면 Dispatcher-Servlet이 요청을 수신하게 됩니다.
http GET localhost:8080/base value=스프링
Dispatcher-Servlet은 요청을 처리할 핸들러를 찾고 해당 객체의 메소드를 호출합니다. 따라서 가장 먼저 어느 컨트롤러가 요청을 처리할 수 있는지를 식별해야 하는데, 해당 역할을 하는 것이 바로 HandlerMapping입니다.
DispatcherServlet → handlerMappings → RequestMappingHandlerMapping → mappingRegistry 에서 관리하고 있습니다.

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
해당 getHandler에서 요청에 맞는 컨트롤러를 찾게 됩니다.
컨트롤러의 구현 방식이 다양하기 때문에 Dispatcher-Servlet이 컨트롤러로 요청을 직접 위임하는 것이 아니라 HandlerAdapter라는 어댑터 인터페이스를 통해 어댑터 패턴을 적용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있도록 하였습니다.
과거
Ruby On Rails가 어노테이션 기반으로 관례를 이용한 프로그래밍을 내세워 혁신을 일으키면서 스프링도 도입하게 되었습니다.
HandlerAdapter는 Controller의 비즈니스 로직 프로세스를 호출합니다.
HandlerAdapter가 컨트롤러로 요청을 위임한 전(ArgumentResolver)/후처리 과정(ReturnValueHandler)이 필요합니다. 대표적으로 인터셉터들을 포함해 요청시 @RequestParam, @RequestBody등을 처리하기 위한 ArgumentResolver 들과 응답 시에 ResponseEntity 의 Body 를 Json으로 직렬화하는 등의 처리를 하는 ReturnValueHandler 등이 핸들러 어댑터에서 처리됩니다. 이러한 작업들을 통해 파라미터가 준비 되면 리플랙션을 이용해 컨트롤러로 요청을 위임합니다.
@RequiredArgsConstructor
@Component
public class StartTimeArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapper objectMapper;
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean hasParameterAnnotation = parameter.hasParameterAnnotation(StartTimeRequestBody.class);
boolean assignableFrom = StartTimeRequest.class.isAssignableFrom(parameter.getParameterType());
return hasParameterAnnotation && assignableFrom;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Base64Request base64Request =
objectMapper.readValue(Objects.requireNonNull(request).getInputStream(), Base64Request.class);
return new StartTimeRequest<>(
base64Request.value(),
LocalDateTime.now()
);
}
}
다음과 같이 등록되어 있는 전처리 과정을 거치게 됩니다. 위에서는 직렬화및 객체를 변환시켜서 반환하는 작업을 하고 있습니다.
컨트롤러는 서비스를 호출하고 작성한 비즈니스 로직을 진행한 뒤 결과를 HandlerAdapter로 반환합니다.
비즈니스 로직이 처리된 후에는 컨트롤러가 반환값을 반환합니다. 응답 데이터를 사용하는 경우에는 주로 ResponseEntity를 반환하게 됩니다.
만약, 응답 페이지를 보여주는 경우라면 String으로 View의 이름을 반환할 수 있습니다.
@GetMapping("/base")
public ResponseEntity<Base64Response> base(@StartTimeRequestBody StartTimeRequest<String> request) {
String encode = encoder.encodeToString(request.body().getBytes());
return **ResponseEntity**.ok(new Base64Response(encode));
}
다음과 같이 비즈니스 로직을 처리한 후 HandlerAdapter로 반환하게 됩니다.
HandlerAdapter는 컨트롤러부터 받은 응답을 응답 처리기인 ReturnValueHandler가 후처리한 후에 Dispatcher-Servlet으로 돌려줍니다. 만약 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태를 설정합니다.
public void handleReturnValue(
@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest
) throws Exception {
// ETC...
}
HttpEntityMethodProcessor 에 있는 handleReturnValue 메서드가 실행되면서 직렬화 및 응답 상태를 설정합니다.
만약, 컨트롤러가 View 이름을 반환하면 ViewResolvers를 통해 View를 반환합니다.
Dispatcher-Servlet을 통해 반환되는 응답은 다시 필터들을 거쳐 클라이언트에게 반환됩니다. 이때 응답이 데이터라면 그대로 반환됩니다.
만약, 응답이 화면이라면 View의 이름에 맞는 View를 찾아서 반환해주는 ViewResolver가 적절한 화면을 내려줍니다.
JSP 혹은 Thymeleaf 인 경우 ViewResolver가 동작합니다.
기본적으로 Spring Boot에서 사용하는 AWS는 Tomcat이며 Default로 설정되어 있는 Worker Thread가 200개입니다. HttpRequest가 들어왔을 때 하나씩 쓰레드를 사용 및 재배정을 진행하고, Request별로 Thread가 별도로 생성되고 이에 따라 각각의 ServletContext를 갖는 것은 분명한데, 이 쓰레드들이 하나의 Contoller 객체를 공유한다는 것이 가능한가?에 대한 의문이 들기 마련입니다.
Controller 객체 하나를 생성하면 객체 자체는 Heap에 생성되지만, 해당 Class의 정보는 Method Area(Permanent Area)에 저장되는 것입니다. 결국 Heap Area이던 Method Area이던 모든 쓰레드가 객체이 Binary Code 정보를 공유할 수 있다는 뜻입니다.
내부적으로 상태를 갖는 것이 없으니, 내부의 상태를 변경할 일이 없고 그저 메소드에 대한 정보만 “같이 공유해서”쓰면 되는 것이기 때문에 동기화에 대한 걱정을 할 필요가 없습니다.
https://velog.io/@betterfuture4/Spring-Dispatcher-Servlet-정리
https://sigridjin.medium.com/servletcontainer와-springcontainer는-무엇이-다른가-626d27a80fe5