SpringMVC之源碼分析--ViewResolver(一)

概述

本章開始進入另外一重要的組件,即視圖組件,Spring MVC處理視圖組件使用兩個主要的接口是ViewResolver和View。根據名稱可知,ViewResolver即視圖解析器,其做用是把邏輯視圖名稱解析爲真正的視圖,而後經過View對象進行渲染。View接口的做用是用於處理視圖進行渲染。web

延用以前的介紹流程,本章分兩部分進行闡述:啓動初始化和請求處理。app

本系列文章是基於Spring5.0.5RELEASE。jsp

ViewResolver初始化

Spring MVC初始化視圖解析器策略與初始化其餘策略同樣,其入口是DispatcherSerlvet的initStrategies(context)方法,代碼以下:學習

/**
 *初始化策略對象
 */
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    // 初始化視圖解析器ViewResolver
    initViewResolvers(context);
    initFlashMapManager(context);
}

看過以前文章的能夠了解,此方法是Spring MVC初始化策略組件的入口。針對視圖解析器組件,調用initViewResolver(context)方法,加載視圖處理策略,該方法源碼以下:this

private void initViewResolvers(ApplicationContext context) {
    // viewResolvers是視圖解析器集合,接收到用戶請求時,從該屬性變量中獲取到Spring MVC使用的視圖解析器
    this.viewResolvers = null;
    // 是否從Spring上下文中加載ViewResolver,detectAllViewResolvers屬性變量默認爲true,可在web部署描述文件中修改
    if (this.detectAllViewResolvers) {
        // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
        // 按接口類型ViewResolver查找所有
        // key爲bean的id(name),value爲bean的class對象
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<>(matchingBeans.values());
            // We keep ViewResolvers in sorted order.
            // 對ViewResolver進行排序,經過Ordered接口實現
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else { 
        try {
            // 從Spring上下文中加載指定名字爲"viewResolver"的bean,做爲視圖解析器
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default ViewResolver later.
        }
    }

    // Ensure we have at least one ViewResolver, by registering
    // a default ViewResolver if no other resolvers are found.
    // 爲了確保至少有一個ViewResolver視圖解析器,Spring MVC配置了默認的ViewResolver
    // 在DispatcherServlet.properties文件中定義,默認爲InternalResourceViewResolver
    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}

至此,Spring MVC即完成加載初始化ViewResolver視圖解析器,即Spring MVC以具有解析處理邏輯視圖名稱的能力。url

ViewResolver使用

ViewResolver的使用是指用戶發起請求到Spring,Spring MVC通過HandlerMapping查找處理的handler,而後經過HandlerAdapter進行適配後處理用戶請求,返回ModelAndView,最後使用ViewResolver對ModelAndView進行解析,即把邏輯視圖名解析爲真正的視圖對象,由視圖對象進行渲染的過程。spa

用戶的請求處理流程由DispatcherServlet的doDispatcher(request,response)方法進行控制,主要代碼以下:debug

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    ... ...
    // 找到請求對應的handler
    mappedHandler = getHandler(processedRequest);
    ... ...
    // 找到對應handler的適配器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    ... ...
    // 執行用戶請求的攔截器前置處理方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // 調用實際的handler處理方法
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    ... ...
    // 設置默認的邏輯視圖名稱
    // 若是handler返回null或者ViewName屬性爲null時,Spring進行取視圖規則爲:前綴+url+後綴
    // 好比訪問url爲:http://localhost:8086/test,前綴配置/WEB-INF/jsp,後綴配置爲.jsp,那麼最終查找的是:/WEB-INF/jsp/test.jsp
    applyDefaultViewName(processedRequest, mv);
    // 執行用戶請求的攔截器後置處理方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    ... ...
    // 
    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 (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);
        }
    }

    // Did the handler return a view to render?
    // 調用render方法
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                    "': assuming HandlerAdapter completed request handling");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

/*
 *渲染ModelAndView
 */
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) {
        // We need to resolve the view name.
        // 根據邏輯視圖名解析視圖,返回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 {
        // No need to lookup: the ModelAndView object contains the actual View object.
        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() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // 調用View的render方法進行視圖渲染
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

@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) {
            // 調用ViewResovler接口入口方法
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

總結

本章概述了Spring MVC加載視圖解析器策略,而且找到了ViewResolver的入口方法,下面的章節繼續學習ViewResolver接口的實現類。code

最後建立了qq羣方便你們交流,可掃描加入,同時也可加我qq:276420284,共同窗習、共同進步,謝謝!對象

相關文章
相關標籤/搜索