1. DispatcherServlet?
과거엔 view(사용자에게 보여지는 영역)단에서 비즈니스 로직을 처리해 코드가 서로 얽혀 유지보수에 어려움이 있었다. 이를 해결하기 위해 나온 패턴이 MVC(Model-View-Controller)패턴이다.
초창기 MVC패턴에선 다양한 요청을 처리하기 위한 Controller가 많아지고 이에 따라 중복되고 공통된 기능을 하는 코드들이 늘어났다. 이를 해결하기 위해 나온 방법이 FrontController다.
FrontController의 역할은 한 회사의 대표와 같다. 대표는 외부에서 들어오는 각각의 요청들을 적절하게 수행할 수 있는 직원들에게 분배한다. 또 본인 혼자서 수행하기에 업무양이 많다면 전무, 상무, 부장 등 간부들에게 지시를 내려 적합한 직원을 찾도록한다. 이렇듯 FrontController 또한 해당 요청에 적합한 Controller를 찾기위해 여러 객체들에게 지시를 내린다.이러한 역할을 하는 FrontController가 바로 스프링의 DispatcherServlet이다.
2. DispatcherServlet의 역할
- 요청이 들어오면 DispatcherServlet은 HandlerMapping을 통해 요청 url에 매핑된 Controller를 찾는다.
- 그리고 해당 Controller에 적합한 Adapter가 존재하는지 목록을 확인하고 존재할 경우 해당 어댑터를 통해 매핑된 Controller를 호출한다.
- 마지막으로 Controller에서 처리된 반환 값을 viewResolver에게 보내 view를 반환한다.
3. DispatcherServlet의 동작 과정
DispatcherSerlvet 또한 아래 그림처럼 최고 조상에 Servlet 인터페이스를 상속 받는다. 그렇기 때문에 Servlet와 같은 라이프 사이클을 갖게 된다.

3.1 초기화(init)
서블릿과 마찬가지로 최초 요청이 오면 초기화를 먼저 진행한다.
Servlet의 init()이 실행된다. 이 init은 GenericServlet을 거쳐 HttpServletBean에서 오버라이딩 되고 initServletBean()을 호출하게 된다.
<Servlet>
public interface Servlet {
void init(ServletConfig config) throws ServletException;
.
.
.
}
<GenericServlet>
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 실제 오버라이딩하게 되는 메서드 -> init(config)에서 호출이 되기때문이다.
public void init() throws ServletException {
// NOOP by default
}
<HttpServletBean>
@Override
public final void init() throws ServletException {
.
.
.
initServletBean();
}
protected void initServletBean() throws ServletException {
}
FrameworkSevlet에서는 initServletBean()을 오버라이딩하고 initWebApplicationContext()을 호출하고 여기선 orRefresh()를 호출한다.
<FrameworkServlet>
@Override
protected final void initServletBean() throws ServletException {
.
.
.
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
.
.
.
}
protected WebApplicationContext initWebApplicationContext() {
.
.
.
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
.
.
.
return wac;
}
onrefresh()는 Spring context(빈 관리)를 refresh한다음 호출된다.
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
onRefresh는 드디어 DispatcherServlet에서 오버라이딩 되고 initStrategies(context)를 호출한다.
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
initStrategies라는 이름과 아래 호출되는 메서드명들을 보면 알 수 있듯이 앞으로 DispatcherServlet이 위임하는 작업을 수행할 객체를 초기화를 하게 된다.
3.2 요청 수행(service)
서블릿과 마찬가지로 요청이 들어오면 service()를 수행한다.
Servlet의 service()는 GenericServlet을 거쳐 HttpServlet에서 오버라이딩 되고 여기서 doGet(), doPost()등을 호출하게 된다.
<HttpServelt>
// 오버라이딩 된 service에서 오버로딩된 service를 호출
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
.
.
.
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req, resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req, resp);
} else {
.
.
.
}
}
doGet, doPost 등은 FrameworkServlet에서 오버라이딩 되고 이러한 do~()시리즈의 메서드는 모두 processRequest(res, reps)를 호출하게 된다. 그리고 processRequest는 doService()를 호출한다.
<FrameworkServlet>
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
.
.
.
try {
doService(request, response);
}
.
.
.
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
doService는 DispatcherServlet에서 오버라이딩 되고 doDispatch를 호출해 요청을 처리하게 된다.
<DispatcherServlet>
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
.
.
.
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
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);
}
}
}
}
4. doDispatch() 이후
doDIspatch()에서는 요청url에 매핑된 Controller를 찾게 된다. 그리고 해당 컨트롤러에 맞는 핸들러 어댑터를 통해 Controller를 호출 한다. Controller에서 처리된 반환값은 ModelAndView에 담겨 반환되고 viewResolver에 전해져 view를 렌더링하게 된다.
아래 이미지와 같은 모든 작업을 DispatcherServlet에서 수행하게 되고 그 안에서도 doDispatch()메서드를 통해 수행된다.

'Spring' 카테고리의 다른 글
Spring - @Transactional 활용해 멀티 스레드 환경을 테스트할 때 주의점 (0) | 2024.05.06 |
---|---|
Spring - 요청 파라미터 LocalDate타입 필드에 바인딩 및 검증 (1) | 2024.04.12 |
Spring - Springboot + Vue3 + MariaDB를 카페24에 배포할 때 Tip(주의사항) (1) | 2023.07.26 |
Spring - ArgumentResolver (0) | 2023.07.22 |