본문 바로가기
Spring

[Spring] ViewResolver 코드 파보기

by doodoom 2023. 5. 4.

0. 이 글을 쓰게 된 이유

최근 Dispatcher Servlet에 대해 공부하면서 ViewResolver에 대해서 궁금증이 생겨 이 글을 쓰게 되었다.

1. ViewResolver란?

ViewResolver는 요청이 들어오면 DispatcherServlet이 핸들러를 찾아서 해당 핸들러가 ModelAndView 객체를 반환한다. DispatcherServlet은 ViewResolver를 List로 가지고있는데, ModelAndView에 있는 View의 name을 활용해서 적절한 View를 찾아서 반환한다.
즉, ViewResolver는 적절한 View를 찾는 역할을 한다.

2. ViewResolver 동작 원리

2.1 ModelAndView 객체 찾아와서 실행한다.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

    try {
        ModelAndView mv = null;

        ...


        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
}

HandlerAdapter를 실행하여 ModelAndView 객체를 찾다온다.

2.2 processDispatchResult 메소드 실행

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {

        // exception에 대한 처리를 한다.
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }

        if (mv != null && !mv.wasCleared()) {
            render(mv, request, response); // render 메소드를 호출
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }

        ...
}

error가 있을 경우 View에 대해서 일괄 처리를 하고 render 메소드를 호출한다.

2.3 render 메소드 실행

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Determine locale for request and apply it to the response.
        Locale locale =
                (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
        response.setLocale(locale);

        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            // // viewName을 통해 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() + "'");
            }
        }
        ...
        view.render(mv.getModelInternal(), request, response);
        ...
}

2.4 resolveViewName 메소드 실행

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
            Locale locale, HttpServletRequest request) throws Exception {

        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
}

DispatcherServlet이 가지고있는 List<ViewResolver>를 순회하며 View 객체를 찾는다.

2.5 ViewResolver Interface

public interface ViewResolver {

    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

resolveViewName메소드 하나만 가지고있다. view의 이름인 ViewName과 지역화를 위한 Locale을 받고있으며, 매핑되는 View객체를 반환한다.

2.6 View Interface

public interface View {

    @Nullable
    default String getContentType() {
        return null;
    }

    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception;
}

getContentType은 application/json 같은 ContentType을 반환한다.
그리고 render 메소드는 view를 사용할 수 있도록 rendering 한다. 여기서 jsp, thymleaf 같은 라이브러리를 이용해 rendering 한다.