DispatcherServlet에서 Dispatcher는 '실행 순서를 스케쥴하는' 의미를 담고 있습니다. 스프링에서 디스패쳐서블릿은 요청이 응답될 수 있도록 요청을 실행 순서에 맞게 처리해 주는 일을 하게됩니다. 톰켓이 요청을 받으면 디스패쳐 서블릿이 톰켓으로부터 요청을 request와 response 객체로 받아서, 적절히 처리하고 응답 해주는 역할을 하게 됩니다.
디스패쳐 서블릿은 모든 서버로 오는 모든 요청을 제일 앞에서 받는데, 이런 역할을 "프론트 컨트롤러"라고 합니다. 그리고 이 컨트롤러가 요청을 "모델"과 "뷰"에 담아 응답하는데, 이런 패턴을 "MVC패턴" 이라고 합니다.
요청을 받게 되는 경로
디스패쳐 서블릿은 메이븐 의존성 중 spring-webmvc 파일에서 소스코드를 찾아볼 수 있습니다.
DispatcherServlet은 FrameworkServlet을 상속받았습니다.
FrameworkServlet이 상속받은 클래스들을 타고 들어가다 보면 결국 HttpServlet을 상속받은 것을 알 수 있습니다.
FrameworkServlet에는 doGet, doPost등 요청을 받는 낯익은 코드를 볼 수 있습니다.
여기 FrameworkServlet의 코드에서 get, post, delete, put 등의 HTTP 요청을 받으면 processRequest() 메소드를 호출하게 됩니다.
processRequest는 doService()메소드를 호출하는데, doService는 DispatcherServlet에 구현되어 있습니다.
이제 DispatcherServlet의 doService메소드에서 doDispatch() 메소드를 실행합니다.
doDispatch () 메소드 소스코드
이 메소드가 이번 포스팅에서 다룰 요청의 실행순서를 스케쥴하는 핵심 메소드입니다. 소스코드를 리뷰해보았습니다.
(소스코드보기)
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch() 메소드의 동작 순서
1. 요청 분석 (multipart)
제일 먼저 하는 일은, 요청을 분석해서 그 요청이 multipart요청인지 판단하게 됩니다. multipart요청은 파일 업로드때 사용하는 요청 타입이죠.
try { processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
2. 핸들러맵핑
그 다음 getHandler 메소드를 실행해서 핸들러를 찾아오게 됩니다.
(핸들러는 개발자가 코딩한, 요청이 왔을때 실행되길 기대하는, 컨트롤러에 정의된 메소드를 말합니다.)
(예시보기)
//예시
//사용자 정의 컨트롤러
@Controller
public class UserController(){
//==핸들러=====
@getMapping("/hi")
public String hi(){
return "hi";
}
//============
}
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
3. 핸들러 어댑터
핸들러맵핑을 통해 찾은 핸들러를 실행시켜줄 핸들러 어댑터를 찾습니다.
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
4. doGet요청인지 확인하고 맞으면 추가작업
핸들러를 실행시키기 전에, doGet요청이면 추가적인 작업을 해줍니다. ex) 캐싱기능..
5. 핸들러어댑터로 실행시키고 결과를 Model and View에 저장
이 때 핸들러어댑터 안에서 우리가 코딩한 핸들러를 호출(invoke)해서 실행시키게 됩니다. 그리고 핸들러의 리턴값을 mv 변수에 담게 됩니다.
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
6. 렌더링하기
mv에 저장된 결과를 바탕으로 디스패쳐서블릿 내부에있는 processDispatchResult() 메소드를 호출해서 렌더링을 처리하게 됩니다.
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
렌더링을 처리하는 메소드를 호출하고,
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
...
render(mv, request, response);
...
}
그 안에서 resource를 탐색해 내 html 파일의 위치를 확인하고 불러와서, VIew에 넣고, ViewResolver로 view를 렌더링하게 됩니다.
읽어주셔서 감사합니다 ^^
'back > spring' 카테고리의 다른 글
[스프링] 스프링을 사용하는 이유 (0) | 2022.11.02 |
---|---|
[스프링] 빈 팩토리를 사용하는 이유 (0) | 2022.11.02 |
[번역]Inversion of Control Containers and the Dependency Injection pattern - Martin Fowler (0) | 2022.05.08 |
[Spring] 핸들러(Handler)란? (1) | 2021.12.24 |
댓글