본 포스팅은 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 보고 정리한 내용입니다.
스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어 있다.
프론트 컨트롤러 → DispatcherServlet
스프링 부트는 DispatcherServlet
을 서블릿은 자동 등록하면서 모든 경로에 대해서 매핑한다.
view.render(mv.getModelInternal(), request, response);
HttpServlet
이 제공하는 service()가 호출된다.DispacherServlet.doDispatch()
를 호출한다.public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
//...
try {
doDispatch(request, response);
}
finally {
//...
//...
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
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); // response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Determine handler adapter for the current request.
// 핸들러 어댑터 조회 - 핸들러를 처리할 수 이쓴 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// dispatcher
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 핸들러 어댑터 실행 -> 핸들러 어댑터를 통해 핸들러 실행 -> ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
//...
// Did the handler return a view to render?
// 뷰 rendering 호출
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 {
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 뷰 Resolver를 통해서 뷰 찾기, View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
//....
}
//...
// 뷰 rendering
view.render(mv.getModelInternal(), request, response);
}
변환
해서 반환* ThymeleafView *
ContentNegotiationViewResolver 를 통해 ThymeleafView 객체를 찾고 찾은 Thymeleaf 객체를 통해 동적 렌더링을 시작한다.
ThymeleafView 객체는 렌더링을 하기 위해 여러가지 정보들을 가지고 있는 Context 객체를 만든다. Context 객체 안에는 Model 정보 뿐 아니라 Locale 과 관련된 정보, Thymeleaf Template 파일을 찾기 위한 TemplateResolver , Request 와 Response 같은 정보도 포함하고 있다.
@Bean
@ConditionalOnMissingBean(name = "thymeleafViewResolver")
ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine);
resolver.setCharacterEncoding(properties.getEncoding().name());
resolver.setContentType(
appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
resolver.setProducePartialOutputWhileProcessing(
properties.getServlet().isProducePartialOutputWhileProcessing());
resolver.setExcludedViewNames(properties.getExcludedViewNames());
resolver.setViewNames(properties.getViewNames());
// This resolver acts as a fallback resolver (e.g. like a
// InternalResourceViewResolver) so it needs to have low precedence
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
resolver.setCache(properties.isCache());
return resolver;
}