重定向

forward前缀

若要返回/WEB-INF/pages/success.jsp,则直接return "success";即可。
若要返回webapp下的helloworld.jsp页面:

相对路径 ../../hello,需return "../../helloworld";
forward前缀,转发一个页面,不会进行拼串。需return "forward:/helloworld.jsp";
格式:  forward:转发的路径

    @RequestMapping("/hello")
    public String hello(){
        return "forward:/helloworld.jsp";
    }
    @RequestMapping("/hello1")
    public String hello1(){
        return "forward:/hello";
    }

redirect前缀

重定向 redirect:重定向的路径 视图解析器不会进行拼串
原生的Servlet重定向/路径需要加上项目名才能成功。
/helloworld.jsp:代表的是从当前项目下开始,SpringMVC会为路径自动的拼接上项目名。

    //重定向到helloworld.jsp页面
    @RequestMapping("/hello2")
    public String hello2(){
        return "redirect:/helloworld.jsp";
    }
    @RequestMapping("/hello3")
    public String hello3(){
        return "redirect:/hello2";
    }

SpringMvc视图解析流程

1、方法执行后的返回值会作为页面地址参考,转发或者重定向到页面 2、视图解析器可能会进行页面地址的拼串; 具体流程 1、任何方法的返回值,最终都会被包装成ModelAndView对象 image.png 2、processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);来到页面的方法 视图渲染流程:将域中的数据在页面展示;页面就是用来渲染模型数据的;

3、调用render(mv, request, response);渲染页面

4、View(interface)与ViewResolver(interface) ViewResolver的作用是根据视图名(方法的返回值)得到View对象; image.png

那么ViewReslover是如何根据方法的返回值(视图名),得到View对象的

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

          //遍历所有的ViewResolver;
        for (ViewResolver viewResolver : this.viewResolvers) {
          //viewResolver视图解析器根据方法的返回值,得到一个View对象;
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
        return null;
    }

resolveViewName(viewName, locale)方法的实现

@Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (!isCache()) {
            return createView(viewName, locale);
        }
        else {
            Object cacheKey = getCacheKey(viewName, locale);
            View view = this.viewAccessCache.get(cacheKey);
            if (view == null) {
                synchronized (this.viewCreationCache) {
                    view = this.viewCreationCache.get(cacheKey);
                    if (view == null) {
                        // Ask the subclass to create the View object.
                         //根据方法的返回值创建出视图View对象;
                        **view = createView(viewName, locale);**
                        if (view == null && this.cacheUnresolved) {
                            view = UNRESOLVED_VIEW;
                        }
                        if (view != null) {
                            this.viewAccessCache.put(cacheKey, view);
                            this.viewCreationCache.put(cacheKey, view);
                            if (logger.isTraceEnabled()) {
                                logger.trace("Cached view [" + cacheKey + "]");
                            }
                        }
                    }
                }
            }
            return (view != UNRESOLVED_VIEW ? view : null);
        }
    }

创建视图对象的方法实现createView(viewName, locale)

image.png

@Override
    protected View createView(String viewName, Locale locale) throws Exception {
        // If this resolver is not supposed to handle the given view,
        // return null to pass on to the next resolver in the chain.
        if (!canHandle(viewName, locale)) {
            return null;
        }
        // Check for special "redirect:" prefix.
        **if (viewName.startsWith(REDIRECT_URL_PREFIX))** {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            **RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());**
            return applyLifecycleMethods(viewName, view);
        }
        // Check for special "forward:" prefix.
        **if (viewName.startsWith(FORWARD_URL_PREFIX))** {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            return **new InternalResourceView(forwardUrl);**
        }
        // Else fall back to superclass implementation: calling loadView.
        //如果没有前缀就使用父类默认创建一个View;
        **return super.createView(viewName, locale);**
    }

image.png image.png

返回View对象:

1、视图解析器得到View对象的流程:所有已经配置了的解析器通过增强for循环尝试根据视图名(方法的返回值)得到View对象(视图对象);若能够得到就返回,得不到就继续进行for循环,切换到下一个视图解析器; 2、调用View对象的render方法

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

InternalResourceView中有renderMergedOutputModel方法

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

将模型中的所有数据取出来全放在request域中

  • 这也就是为什么隐含域中的数据能够在request域中
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            String modelName = entry.getKey();
            Object modelValue = entry.getValue();
            if (modelValue != null) {
                request.setAttribute(modelName, modelValue);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                            "] to request in view with name '" + getBeanName() + "'");
                }
            }
            else {
                request.removeAttribute(modelName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Removed model object '" + modelName +
                            "' from request in view with name '" + getBeanName() + "'");
                }
            }
        }
    }

总结

视图解析器只是为了得到View对象 对于转发操作(将数据模型全部放在请求域中)或者重定向到页面的操作是视图(View)对象做的事情 因此,视图对象才是真正渲染视图 image.png image.png


不论控制器返回一个String,ModelAndView,View都会转换为ModelAndView对象,由视图解析器解析视图,然后,进行页面的跳转 image.png

流程图

image.png

视图和视图解析器

  • 请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们- 装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图 Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart等各种表现形式的视图
  • 对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦

视图

  • 图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
  • 为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口
  • 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题

常用的视图实现类

image.png

视图解析器

SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。

  • 视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
  • 所有的视图解析器都必须实现 ViewResolver 接口:

常用的视图解析器实现类

image.png

  • 程序员可以选择一种视图解析器或混用多种视图解析器
  • 每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。
  • SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常
  • InternalResourceViewResolver JSP 是最常见的视图技术,可以使用 InternalResourceViewResolve作为视图解析器