1. DispatcherSevlet이란? 🌺
🌱 Dispatcher란?
- dispatch: 보내다
- dispatcher: (열차, 버스 등이 정시 출발하도록 관리하는) 운행 관리원
🌱 Servlet이란?
- 웹 서버 내에서 실행되는 작은 Java 프로그램
- 일반적으로 HTTP를 통해 웹 클라이언트의 요청을 수신 & 응답
🌱 DispatcherServlet이란?
dispatcher + servlet 단어의 조합에서 추측해보면 클라이언트의 요청 수신/응답을 관리하는 작은 Java 프로그램 정도로 추측할 수 있을 것 같다.
스프링의 웹 MVC 프레임워크는 요청 기반(request-driven)이다. 컨트롤러에게 요청을 보내고(dispatch) 웹 애플리케이션 개발에 용이한 기능 등을 제공하는 중앙 서블릿이 존재한다. Spring에서 이 중앙 서블릿은 DispatcherServlet이 담당하는데 중앙 서블릿 외에도 다양한 역할을 수행한다. 애플리케이션에 요청이 들어온다고 가정해보자. 그림의 하양, 파랑, 노랑, 빨강 상자는 controller의 내부에 있는 코드이다.
각 요청은 적합한 controller를 찾아서 로직을 찾아 수행하고 응답값을 반환 받는다. 헌데 여기서 모든 controller에 공통적으로 처리해야할 부분(하얀 상자)이 존재한다. 현재 들어온 request가 이 controller에서 처리하는 것이 맞는지에 대한 로직일수도 있고 Exception이 발생한 경우 어떻게 처리하는지에 대한 로직일 수도 있다. 여기서 등장한게 front controller 패턴이다.
모든 요청을 front controller에게 보내 공통 로직을 수행한다. 이후 적합한 controller를 찾아 해당 controller의 로직을 수행시키는 역할도 한다. 여기서 이 front controller가 Spring에서의 DispatcherServlet이다.
2. DispatcherServlet의 역할 🌺
🌱 DispatcherServlet 실행 과정 - 이론편
먼저 DispatcherServlet의 실행 과정에 대해 간략하게 살펴보겠다. (일부 과정은 생략하고 그렸다.)
- HandlerMapping을 통해 요청 url에 매핑된 Handler 조회
- Handler를 처리할 수 있는 HandlerAdapter 조회
- HandlerAdapter 실행
- HandlerAdapter가 Handler(일반적으로 Controller) 실행
- HandlerAdapter가 4의 결과를 ModelAndView 타입으로 변환해서 반환
- ViewResolver가 5에서 반환된 String 기반의 View 이름을 실제 View로 전환
- 전환한 View를 반환
- View를 이용해 렌더링
당장은 이해할 필요 없다. 아래 글을 좀 더 읽고 나서 다시 보러오자.
🌱 DispatcherServlet의 역할
여기서 DispatcherServlet이 어떤 일들을 하는지 좀 더 구체적으로 살펴보려고 한다. DispatcherServlet은 요청을 매핑하고 뷰를 처리하고, 예외를 핸들링하는 등 다양한 일을 한다. 이 일들을 처리하기 위해 special bean 이라는 친구들을 이용한다.
Bean Type | Explanation |
HanlderMapping | 요청을 처리할 핸들러와 인터셉터 목록을 가짐 |
HandlerAdapter | DispatcherServlet이 요청에 매핑되는 Handler를 호출할 수 있도록 돕는 역할 Handler가 실제로 호출되는 방식과 상관 없도록 도움 |
HandlerExceptionResolver | 예외 해결을 위한 전략 |
ViewResolver | 핸들러에서 반환된 String 기반의 View 이름을 실제로 응답에서 렌더링할 View로 전환 |
LocaleResolver, LocaleContextResolver |
client가 사용중인 Locale과 time zone을 확인 |
ThemeResolver | 웹 애플리케이션에서 사용 가능한 theme 확인 |
MultipartResolver | multi-part 요청을 parsing하기 위한 추상화 |
FlashMapManager | redirect를 통해 한 요청에서 다른 요청으로 attribute를 전달할 때 사용하는 입출력 FlashMap을 저장하고 검색 |
Application은 request 처리를 위해 필요한 Special Bean Types를 정의할 수 있다. 각 Special Bean에 대해 WebApplicationContext를 확인하고 일치하는 타입이 없으면 DispatcherSerlvlet.properties에 나열된 기본 유형으로 대체된다.
🌱 DispatcherServlet의 실행 과정 - 코드편
DispatcherServlet의 핵심 로직은 doDispatch 메서드를 살펴보면 된다. 그런데 doDispatch는 doService 메서드에 의해 호출되는 메서드이다. 그렇다면 doService는 무엇을 하는 메서드일까?
doService 메서드는 DispatcherServlet이 상속하는 FrameworkServlet의 메서드를 오버라이드한 것이다. doService()에서는 DispatcherServlet와 관련된 여러 attribute를 세팅하는 과정을 거친 후 실제로 dispatch 하기 위해 doDispatch()가 호출된다.
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// set attributes
doDispatch(request, response);
// ...
}
이제 본격적으로 doDispatch를 까보자. 읽기 쉽게 하기 위해 일부 코드를 제거하거나 주석으로 대체했다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// Multipart 검증
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Handler 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Handler Adapter 조회
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Handler가 지원하는 경우 마지막 수정된 header 처리
// 등록된 interceptor의 preHandle 메서드 실행
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Handler Adapter 실행 -> HandlerAdapter가 Handler 실행 -> ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ModelAndView에 view 이름 세팅
applyDefaultViewName(processedRequest, mv);
// 등록된 interceptor의 postHandle 메서드 실행
mappedHandler.applyPostHandle(processedRequest, response, mv);
} // catch로 Exception 또는 Throwable을 잡아 dispatcherException에 세팅
// 뷰 렌더링 호출
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} // catch & finally
}
대충의 흐름을 보면 Handler 조회 -> Handler Adapter 조회 -> interceptor preHandle 실행 -> Handler Adapter 실행(Handler 실행) -> interceptor postHandle 실행 -> View 렌더링 호출 이런 순으로 로직이 실행되고 있다. 좀 더 디테일한 과정은 spring docs를 참고해보라. 이제 위에서 봤던 그림을 다시 보자. 아직 감이 안잡히면 직접 DispatcherServlet을 구현하는 방법도 있다. 박재성님의 자바 웹 프로그래밍 Next Step 책이나 우테코의 mvc 미션을 참고하면 좋다.
참고
- https://docs.spring.io/spring-framework/docs/3.0.0.RC2/spring-framework-reference/html/ch15s02.html
- https://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/DispatcherServlet.html
- https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet
'Develop > Spring+JPA' 카테고리의 다른 글
[Spring] Local Transaction vs Global Transaction (8) | 2022.10.09 |
---|---|
[Spring] 내 테스트에만 stub 적용하기 😈 (2) | 2022.10.03 |
[토비의 스프링] Chapter 1. 오브젝트와 의존관계 (0) | 2022.09.10 |
[Mockito] LocalDateTime.now() 테스트하기 (0) | 2022.08.15 |
[Spring] ResponseEntity vs DTO (3) | 2022.08.11 |