spring mvc DispatcherServlet詳解之四---視圖渲染過程

整個spring mvc的架構以下圖所示:html

如今來說解DispatcherServletDispatcherServlet的最後一步:視圖渲染。視圖渲染的過程是在獲取到ModelAndView後的過程。java

視圖渲染的過程:web

DispatcherServlet.javaspring

doService()--->doDispatch()--->processDispatchResult()--->render()express

processDispatchResult():主要處理異常、請求狀態及觸發請求完成事件,圖的渲染工做交給了render().api

render()渲染過程以下:服務器

1. 判斷ModelAndView中view是否爲view name,沒有獲取其實例對象:若是是根據name,若是是則須要調用resolveViewName從視圖解析器獲取對應的視圖(View)對象;不然ModelAndView中使用getview方法獲取view對象。架構

2. 而後調用view的render()方法。mvc

具體代碼以下:oracle

 

/**
     * Render the given ModelAndView.
     * <p>This is the last stage in handling a request. It may involve resolving the view by name.
     * @param mv the ModelAndView to render
     * @param request current HTTP servlet request
     * @param response current HTTP servlet response
     * @throws ServletException if view is missing or cannot be resolved
     * @throws Exception if there's a problem rendering the view
     */
    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.resolveLocale(request);
        response.setLocale(locale);

        View view;
        if (mv.isReference()) {
            // We need to resolve the view name.
            view = resolveViewName(mv.getViewName(), 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 {
            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;
        }
    }

那麼view 是如何渲染的?咱們來看看view的定義:

org.springframework.web.servlet
Interface View

All Known Subinterfaces: 
SmartView 
All Known Implementing Classes: 
AbstractAtomFeedView, AbstractExcelView, AbstractFeedView, AbstractJasperReportsSingleFormatView, AbstractJasperReportsView, AbstractJExcelView, AbstractPdfStamperView, AbstractPdfView, AbstractRssFeedView, AbstractTemplateView, AbstractUrlBasedView, AbstractView, ConfigurableJasperReportsView, FreeMarkerView, InternalResourceView, JasperReportsCsvView, JasperReportsHtmlView, JasperReportsMultiFormatView, JasperReportsPdfView, JasperReportsXlsView, JstlView, MappingJackson2JsonView, MappingJacksonJsonView, MarshallingView, RedirectView, TilesView, TilesView, VelocityLayoutView, VelocityToolboxView, VelocityView, XsltView 

--------------------------------------------------------------------------------


public interface ViewMVC View for a web interaction. Implementations are responsible for rendering content, and exposing the model. A single view exposes multiple model attributes. 
This class and the MVC approach associated with it is discussed in Chapter 12 of Expert One-On-One J2EE Design and Development by Rod Johnson (Wrox, 2002). 

View implementations may differ widely. An obvious implementation would be JSP-based. Other implementations might be XSLT-based, or use an HTML generation library. This interface is designed to avoid restricting the range of possible implementations. 

Views should be beans. They are likely to be instantiated as beans by a ViewResolver. As this interface is stateless, view implementations should be thread-safe.

spring提供瞭如此多的視圖,那麼確定的是也會有不少視圖解析器:

org.springframework.web.servlet
Interface ViewResolver

All Known Implementing Classes: 
AbstractCachingViewResolver, AbstractTemplateViewResolver, BeanNameViewResolver, ContentNegotiatingViewResolver, FreeMarkerViewResolver, InternalResourceViewResolver, JasperReportsViewResolver, ResourceBundleViewResolver, TilesViewResolver, TilesViewResolver, UrlBasedViewResolver, VelocityLayoutViewResolver, VelocityViewResolver, XmlViewResolver, XsltViewResolver 
Functional Interface: 
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. 

--------------------------------------------------------------------------------


public interface ViewResolverInterface to be implemented by objects that can resolve views by name. 
View state doesn't change during the running of the application, so implementations are free to cache views. 

Implementations are encouraged to support internationalization, i.e. localized view resolution.

其中,針對JSP提供的InternalResourceViewResolver與InternalResourceView。

咱們先看一下view的render方法是什麼樣子的?

根據InternalResourceView的繼承關係:

最終找到render方法在AbstractView中,以下代碼所示:

    /**
     * Prepares the view given the specified model, merging it with static
     * attributes and a RequestContext attribute, if necessary.
     * Delegates to renderMergedOutputModel for the actual rendering.
     * @see #renderMergedOutputModel
     */
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isTraceEnabled()) {
            logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
                " and static attributes " + this.staticAttributes);
        }

        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        prepareResponse(request, response);
        renderMergedOutputModel(mergedModel, request, response);
    }

流程以下:

    建立一個動態值和靜態屬性的map;

    設置response 報文頭;

    把渲染view的工做放到renderMergedOutputModel()實現中,這個留給InternalResourceView來實現。

咱們看看這個實現:

/**
     * Render the internal resource given the specified model.
     * This includes setting the model as request attributes.
     */
    @Override
    protected void renderMergedOutputModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // Determine which request handle to expose to the RequestDispatcher.
        HttpServletRequest requestToExpose = getRequestToExpose(request);

        // Expose the model object as request attributes.
        exposeModelAsRequestAttributes(model, requestToExpose);

        // Expose helpers as request attributes, if any.
        exposeHelpers(requestToExpose);

        // Determine the path for the request dispatcher.
        String dispatcherPath = prepareForRendering(requestToExpose, response);

        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
        if (rd == null) {
            throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                    "]: Check that the corresponding file exists within your web application archive!");
        }

        // If already included or response already committed, perform include, else forward.
        if (useInclude(requestToExpose, response)) {
            response.setContentType(getContentType());
            if (logger.isDebugEnabled()) {
                logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
            }
            rd.include(requestToExpose, response);
        }

        else {
            // Note: The forwarded resource is supposed to determine the content type itself.
            if (logger.isDebugEnabled()) {
                logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
            }
            rd.forward(requestToExpose, response);
        }
    }

流程能夠概括爲如下幾步:

1. 包裝request,供RequestDispatcher來使用;

2. 將map中的屬性和值做爲屬性放入包裝的request;

3. 將不一樣實現類的helper放入包裝的request中;

4. 渲染前的準備,肯定request dispatcher要跳向(或者inclue)的路徑

5. 獲取request dispatcher。

6. 根據request中是否包含include uri屬性來確實是forward或者include方法。

    forward是跳向服務器的servlet, JSP文件, 或者 HTML文件。

  Includes the content of a resource (servlet, JSP page,HTML file) in the response.

注意,在上述流程中出現了RequestDispatcher,那麼這類的做用是什麼呢?

getRequestDispatcher

RequestDispatcher getRequestDispatcher(java.lang.String path)
Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path. A RequestDispatcher object can be used to forward a request to the resource or to include the resource in a response. The resource can be dynamic or static.
The pathname specified may be relative, although it cannot extend outside the current servlet context. If the path begins with a "/" it is interpreted as relative to the current context root. This method returns null if the servlet container cannot return a RequestDispatcher.

The difference between this method and ServletContext#getRequestDispatcher is that this method can take a relative path.

簡潔的來講,

1. RequestDispatcher 是一個包裝器,它將制定路徑的(靜態或者動態)資源包裝起來。RequestDispatcher 能夠用於將一個請求分發給指定的資源或者包裹響應報文中的資源。
2. RequestDispatcher 的獲取,有這種形式,一種使用ServletRequest.getRequestDispatcher(java.lang.String path). 另外一種是servletContext.getRequestDispatcher(java.lang.String path);不一樣之處在於:前面的方法支持相對路徑,以'/'做爲當前上下文的跟路徑;後一種不支持後一種不支持相對路徑。

小結:

能夠看到視圖的渲染過程是把model包裝成map形式經過request的屬性帶到服務器端。

相關文章
相關標籤/搜索