SpringMVC学习系列----3---视图解析流程分析

本篇主要讲述SpringMVC如何解析、渲染视图并转发返回结果对象。

请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返回String,view或者ModelMap等类型的处理方法,SpringMVC也会在内部将他们装配成一个ModelAndView对象。

它包含了逻辑名和模型对象,其中的model可能为 { }空。


首先说明springmvc.xml中视图解析器配置注意事项:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    
    
  • 1
  • 2
  • 3
  • 4

说明一下,如果前缀为 /WEB-INF/views/,那么后台return视图名”/success” or “success”均映射到 /WEB-INF/views/success.jsp ;

如果前缀 为 /WEB-INF/views,那么后台return视图名”/success”,将会映射到/WEB-INF/views/success.jsp;如果后台return视图名”success”,将会报错!


获取MV方法源码如下:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

        modelFactory.updateModel(webRequest, mavContainer);

        if (mavContainer.isRequestHandled()) {
            return null;
        }
        ModelMap model = mavContainer.getModel();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

SpringMVC借助视图解析器得到最终的视图对象,最终的视图对象可能是JSP或者Excel等等。

对于最终采用何种视图对象对模型数据进行渲染,处理器并不关心。处理器的工作重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。

首先看几个概念:

【视图】

  • 视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户;

  • 为了实现视图模型和具体实现技术的解耦,Spring在org.springframwork.web.servlet包中定义了一个高度抽象的View接口

这里写图片描述

  • 视图对象由视图解析器负责实例化,由于他们是无状态的,所以不存在线程安全的问题。

  • 常见的视图实现类

    • JstlView extends InternalResourceView

这里写图片描述

【视图解析器】

  • SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring Web 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。

  • 每一种映射策略对应一个具体的视图解析器实现类。

  • 视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。

  • 所有的视图解析器都必须实现ViewResolver接口。

这里写图片描述

  • 常见的视图解析器实现类

这里写图片描述

  • 可以选择一种或多种视图解析器,可以通过其order属性指定解析器的优先顺序,order越小优先级越高。

  • SpringMVC会按照视图解析器顺序的优先次序进行解析,直到返回视图对象。若无,则抛出ServletException异常。


debug源码分析流程:

  • 首先进入DispatcherServlet.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;
            //看这里,定义一个ModelAndView对象
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    //获取MV对象
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            //处理转发结果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

是不是觉得很简单?
只有三步,定义MV,实例化MV,转发!


看具体分析如下:

这里进行MV对象的实例化

// Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    //获取MV对象
    
    
  • 1
  • 2
  • 3

从上述方法到return SUCCESS 期间所经过的方法处理:
【就是为了实例化MV】
这里写图片描述

此时mv中model为空,view为success

这里写图片描述

进入转发结果处理方法,在该方法中进行视图渲染。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, 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?
        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);
        }
    }
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

进入视图渲染方法:

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 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;
        }
    }

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

进入视图对象解析方法:

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

        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
        return null;
    }
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以看到视图解析器有两个(与SpringMVCxml配置一致):

这里写图片描述

SpringMVC.xml配置:

<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
        //其order属性默认为integer的最大值,故其他解析器只要定义值就可以
    </bean>

    <!-- ****** 配置视图  BeanNameViewResolver 解析器: 使用视图的名字来解析视图 ************ -->
    <!-- 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"></property>
    </bean>
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

首先使用InternalResourceViewResolver:

这里写图片描述

此时得到的view对象:

这里写图片描述

可以看到拿到了name和转发的URL。

  • 为何为JstlView而不是XML配置的InternalResourceViewResolver默认对应的InternlResourceView?

  • 因为JSP页面使用fmt等JSTL标签!SpringMVC会自动使用InternlResourceView的子类 — JstlView !!!

拿到了视图之后,就该进行视图渲染了 !

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);
    }
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里写图片描述

如图上红线表明,先准备数据,再进行数据渲染。不过由于后台方法未传入model数据,所以此时mergedModel仍为空({ })!

不多说,进入renderMergedOutputModel( )方法!

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);
        //看到没,你的model在request中以("key":value)形式存在

        // 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

方法里面第二句:

exposeModelAsRequestAttributes(model, requestToExpose);
    
    
  • 1

看说明如下:

Expose the model objects in the given map as request attributes. 
Names will be taken from the model Map. 
This method is suitable for all resources reachable by javax.servlet.RequestDispatcher.
    
    
  • 1
  • 2
  • 3

意思是说,把map中的数据以(attr : vlaue)的形式曝光给request,你在页面可以使用 诸如${requestScope.attr}的形式获取value!!!

拿到RequestDispatcher:

RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
    
    
  • 1

转发!!!

rd.forward(requestToExpose, response);
    
    
  • 1

之后的事情,JSP解析等等,就是服务器的事情了!!!


Tips:

视图的名字(success)是MV给的,视图里面的model(数据)也是MV给的,MV是Dispatcher给的,视图的URL是视图解析器给的(将逻辑视图success 转换为真实的物理视图 /WEB-INF/views/success.jsp)!!

        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
            </div>

本篇主要讲述SpringMVC如何解析、渲染视图并转发返回结果对象。

请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返回String,view或者ModelMap等类型的处理方法,SpringMVC也会在内部将他们装配成一个ModelAndView对象。

它包含了逻辑名和模型对象,其中的model可能为 { }空。


首先说明springmvc.xml中视图解析器配置注意事项:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
  
  
  • 1
  • 2
  • 3
  • 4

说明一下,如果前缀为 /WEB-INF/views/,那么后台return视图名”/success” or “success”均映射到 /WEB-INF/views/success.jsp ;

如果前缀 为 /WEB-INF/views,那么后台return视图名”/success”,将会映射到/WEB-INF/views/success.jsp;如果后台return视图名”success”,将会报错!


获取MV方法源码如下:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

        modelFactory.updateModel(webRequest, mavContainer);

        if (mavContainer.isRequestHandled()) {
            return null;
        }
        ModelMap model = mavContainer.getModel();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

SpringMVC借助视图解析器得到最终的视图对象,最终的视图对象可能是JSP或者Excel等等。

对于最终采用何种视图对象对模型数据进行渲染,处理器并不关心。处理器的工作重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。

首先看几个概念:

【视图】

  • 视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户;

  • 为了实现视图模型和具体实现技术的解耦,Spring在org.springframwork.web.servlet包中定义了一个高度抽象的View接口

这里写图片描述

  • 视图对象由视图解析器负责实例化,由于他们是无状态的,所以不存在线程安全的问题。

  • 常见的视图实现类

    • JstlView extends InternalResourceView

这里写图片描述

【视图解析器】

  • SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring Web 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。

  • 每一种映射策略对应一个具体的视图解析器实现类。

  • 视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。

  • 所有的视图解析器都必须实现ViewResolver接口。

这里写图片描述

  • 常见的视图解析器实现类

这里写图片描述

  • 可以选择一种或多种视图解析器,可以通过其order属性指定解析器的优先顺序,order越小优先级越高。

  • SpringMVC会按照视图解析器顺序的优先次序进行解析,直到返回视图对象。若无,则抛出ServletException异常。


debug源码分析流程:

  • 首先进入DispatcherServlet.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;
            //看这里,定义一个ModelAndView对象
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    //获取MV对象
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            //处理转发结果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

是不是觉得很简单?
只有三步,定义MV,实例化MV,转发!


看具体分析如下:

这里进行MV对象的实例化

// Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    //获取MV对象
  
  
  • 1
  • 2
  • 3

从上述方法到return SUCCESS 期间所经过的方法处理:
【就是为了实例化MV】
这里写图片描述

此时mv中model为空,view为success

这里写图片描述

进入转发结果处理方法,在该方法中进行视图渲染。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, 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?
        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);
        }
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

进入视图渲染方法:

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 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;
        }
    }

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

进入视图对象解析方法:

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

        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
        return null;
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以看到视图解析器有两个(与SpringMVCxml配置一致):

这里写图片描述

SpringMVC.xml配置:

<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
        //其order属性默认为integer的最大值,故其他解析器只要定义值就可以
    </bean>

    <!-- ****** 配置视图  BeanNameViewResolver 解析器: 使用视图的名字来解析视图 ************ -->
    <!-- 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"></property>
    </bean>
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

首先使用InternalResourceViewResolver:

这里写图片描述

此时得到的view对象:

这里写图片描述

可以看到拿到了name和转发的URL。

  • 为何为JstlView而不是XML配置的InternalResourceViewResolver默认对应的InternlResourceView?

  • 因为JSP页面使用fmt等JSTL标签!SpringMVC会自动使用InternlResourceView的子类 — JstlView !!!

拿到了视图之后,就该进行视图渲染了 !

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);
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里写图片描述

如图上红线表明,先准备数据,再进行数据渲染。不过由于后台方法未传入model数据,所以此时mergedModel仍为空({ })!

不多说,进入renderMergedOutputModel( )方法!

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);
        //看到没,你的model在request中以("key":value)形式存在

        // 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

方法里面第二句:

exposeModelAsRequestAttributes(model, requestToExpose);
  
  
  • 1

看说明如下:

Expose the model objects in the given map as request attributes. 
Names will be taken from the model Map. 
This method is suitable for all resources reachable by javax.servlet.RequestDispatcher.
  
  
  • 1
  • 2
  • 3

意思是说,把map中的数据以(attr : vlaue)的形式曝光给request,你在页面可以使用 诸如${requestScope.attr}的形式获取value!!!

拿到RequestDispatcher:

RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
  
  
  • 1

转发!!!

rd.forward(requestToExpose, response);
  
  
  • 1

之后的事情,JSP解析等等,就是服务器的事情了!!!


Tips:

视图的名字(success)是MV给的,视图里面的model(数据)也是MV给的,MV是Dispatcher给的,视图的URL是视图解析器给的(将逻辑视图success 转换为真实的物理视图 /WEB-INF/views/success.jsp)!!

        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
            </div>

猜你喜欢

转载自blog.csdn.net/u012150590/article/details/80280575