스프링 MVC 이전에는 공통 처리가 어렵다는 문제가 있었다.
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
String viewPath = "/WEB-INF/views/new-form.jsp";
기능이 복잡해질 수 록 컨트롤러에서 공통으로 처리해야 하는 부분이 점점 더 많이 증가할 것이다. 공통 기능을 한 번에 처리할 수 없을까?
DispatcherServlet
HandlerMapping
HandlerAdapter
ModelAndView
ViewResolver
View
org.springframework.web.servlet.DispatcherServlet
상속 구조 - DispatcherServlet
→ FrameworkServlet
→ HttpServletBean
→ HttpServlet
요청 흐름
HttpServlet
이 제공하는 serivce()
가 호출된다.DispatcherServlet
의 부모인 FrameworkServlet
에서 service()
를 오버라이드
해두었다.FrameworkServlet.service()
를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch()
가 호출된다.DispacherServlet.doDispatch()
핵심만 요약한 소스 코드
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);
**// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);**
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
**// 2. 핸들러 어댑터 조회
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());**
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
**// 3. 핸들러 어댑터 실행 -> 4. 어댑터로 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());**
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 결과 처리
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
// ...
}
private void processDispatchResult(
HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (mv != null && !mv.wasCleared()) {
**// 뷰 렌더링 호출
render(mv, request, response);**
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
}
protected void render(
ModelAndView mv, HttpServletRequest request, HttpServletResponse response
) throws Exception {
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
**// 6. 뷰 리졸버를 통해 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);**
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
}
else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'");
}
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
**// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);**
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
InternalResourceViewResolver
가 자동 등록되고, 사용된다.InternalResourceView(JstlView)
를 반환하는데, 내부에 forward()
로직이 있다.컨트롤러가 호출되려면 다음 2가지가 필요하다.
Controller
인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.RequestMappingHandlerMapping
: 애노테이션 기반의 컨트롤러인 @RequestMapping
에서 사용BeanNameUrlHandlerMapping
: 스프링 빈의 이름으로 핸들러를 찾는다.RequestMappingHandlerAdapter
: 애노테이션 기반의 컨트롤러인 @RequestMapping
에서 사용HttpRequestHandlerAdapter
: HttpRequestHandler
처리SimpleControllerHandlerAdapter
: Controller
인터페이스(애노테이션X, 과거에 사용)
처리**@RequestMapping**
RequestMappingHandlerMapping
,
RequestMappingHandlerAdapter
이다. @RequestMapping
의 앞글자를 따서 만든 이름인데, 이것이 바로 지금 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터이다. 실무에서는 99.9% 이 방식의 컨트롤러를 사용한다.DispatcherServlet
코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다는 점이다.HanderMapping
, HandlerAdapter
등등DispatcherServlet
에 등록하면 커스텀 컨트롤러도 구현할 수 있다.