| 视图和视图解析器 
 1.基本介绍
 
 
 2.自定义视图
 在SpringMVC中的目标方法,最终返回的都是一个视图(有各种视图)
 注意,这里的视图是一个类对象,不是一个页面!!
 
返回的视图都会由一个视图解析器来处理(视图解析器有很多种)
  
 
 2.1为什么需要自定义视图
 
 
 2.2应用实例
 在默认情况下,我们都是返回默认的视图,然后返回的视图交由 SpringMVC 的 InternalResourcesViewResolver 默认视图解析器来处理的:
  
在实际开发中,因为业务需求,我们有时候需要自定义视图解析器
视图解析器可以配置多个,按照指定的顺序来对视图进行解析。如果上一个视图解析器不匹配,下一个视图解析器就会去解析视图,以此类推。
 
 执行流程:
 
  2.3创建自定义视图的步骤
 view.jsp,请求到 Handler复制代码<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>自定义视图测试</title></head><body><h1>自定义视图测试</h1><a target="_blank" href="https://www.cnblogs.com/goods/buy">点击到自定义视图</a></body></html>
GoodsHandler.java复制代码package com.li.web.viewresolver;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;/** * @author 李 * @version 1.0 */@RequestMapping(value = "/goods")@Controllerpublic class GoodsHandler {    @RequestMapping(value = "/buy")    public String buy(){        System.out.println("----------buy()---------");        return "liView";//自定义视图名    }}
自定义视图 MyView.java复制代码package com.li.web.viewresolver;import org.springframework.stereotype.Component;import org.springframework.web.servlet.view.AbstractView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.Map;/** * @author 李 * @version 1.0 * 1. MyView 继承了AbstractView,就可以作为了一个视图使用 * 2. @Component(value = "myView") ,该视图会注入到容器中,id为 liView */@Component(value = "liView")public class MyView extends AbstractView {    @Override    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {        //1.完成视图渲染        System.out.println("进入到自己的视图...");        //2.并且确定我们要跳转的页面,如 /WEB-INF/pages/my_view.jsp        /*         * 1.下面就是请求转发到 /WEB-INF/pages/my_view.jsp         * 2.该路径会被springmvc解析成 /web工程路径/WEB-INF/pages/my_view.jsp         */        request.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")<context:component-scan base-package="com.li.web"/><bean >        <property name="prefix" value="/WEB-INF/pages/"/>    <property name="suffix" value=".jsp"/></bean><bean >    <property name="order" value="99"/></bean><mvc:annotation-driven></mvc:annotation-driven><mvc:default-servlet-handler/>.forward(request, response);    }}
结果页面 my_view.jsp复制代码<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>my_view</title></head><body><h1>进入到my_view页面</h1><p>从自定义视图来的...</p></body></html>
springDispatcherServlet-servlet.xml 配置自定义视图解析器复制代码<context:component-scan base-package="com.li.web"/><bean >        <property name="prefix" value="/WEB-INF/pages/"/>    <property name="suffix" value=".jsp"/></bean><bean >    <property name="order" value="99"/></bean><mvc:annotation-driven></mvc:annotation-driven><mvc:default-servlet-handler/>
测试,访问view.jsp,点击超链接
  
成功跳转到 my_view.jsp
  
后台输出如下:说明整个执行流程如图所示。
  
 
 
 2.4Debug源码-自定义视图解析器执行流程
 自定义一个视图:创建一个 View 的 bean,该 bean 需要继承自 AbstractView,并实现renderMergedOutputModel方法
并把自定义 View 加入到 IOC 容器中
自定义视图的视图解析器,使用 BeanNameViewResolver,这个视图解析器也需要配置到 ioc 容器文件中
BeanNameViewResolver 的调用优先级需要设置一下,设置 order 比 Integer.MAX_VALUE 小的值,以确保其在默认的视图解析器之前被调用
 
 自定义视图-工作流程:
 
 
 SpringMVC 调用目标方法,返回自定义 View 在 IOC 容器中的 id
SpringMVC 调用 BeanNameViewResolver 视图解析器:从 IOC 容器中获取返回 id 值对应的 bean,即自定义的 View 的对象
SpringMVC 调用自定义视图的 renderMergedOutputModel 方法,渲染视图
说明:如果 SpringMVC 调用 Handler 的目标方法时,返回的自定义 View ,在 IOC 容器中的 id 不存在,则仍然按照默认的视图解析器机制处理。
 Debug-01(1)在GoodsHandler的目标方法中打上断点:
 
  (2)点击debug,访问view.jsp,点击超链接,可以看到后台光标跳转到断点处: 
  (3)在源码 BeanNameViewResolver 的 resolveViewName 方法处打上断点: 
  (4)点击Resume,光标跳转到了这个断点处,viewName 的值就是自定义视图对象的 id:这里完成视图解析 
  resolveViewName 方法如下: 复制代码@Override@Nullablepublic View resolveViewName(String viewName, Locale locale) throws BeansException {   //获取ioc容器对象   ApplicationContext context = obtainApplicationContext();   //如果容器对象中不存在 目标方法返回的自定义视图对象id   if (!context.containsBean(viewName)) {      // Allow for ViewResolver chaining...      //就返回null,让默认的视图解析器处理该视图      return null;   }   //判断自定义的视图是不是 org.springframework.web.servlet.View 类型   if (!context.isTypeMatch(viewName, View.class)) {      //如果不是      if (logger.isDebugEnabled()) {         logger.debug("Found bean named '" + viewName + "' but it does not implement View");      }      // Since we're looking into the general ApplicationContext here,      // let's accept this as a non-match and allow for chaining as well...      return null;   }   //如果是,就返回这个自定义视图对象   return context.getBean(viewName, View.class);}
 (5)在自定义视图对象里打上断点: 
  (6)点击 resume,光标跳转到该断点:在这里完成视图渲染,并转发到结果页面 
   (7)最后由 tomcat 将数据返回给客户端: 
  2.5Debug源码-默认视图解析器执行流程 
 将默认视图解析器的优先级调高:
 
  debug-02(1)仍然在GoodsHandler中添加断点:
 
  (2)浏览器访问 view.jsp,可以看到后台光标跳转到了断点处: 
  (3)分别在默认视图解析器(InternalResourceViewResolver)和自定义视图解析器(BeanNameViewResolver) 中的方法中打上断点: 
   (4)点击resume,可以看到光标先跳到了默认视图解析器的 buildView 方法中:因为默认解析器的优先级在之前设置为最高。 
  buildView 方法: 这个 View 对象的 url 是按照你配置的前缀和后缀,拼接完成的 url复制代码@Overrideprotected AbstractUrlBasedView buildView(String viewName) throws Exception {   //根据目标方法返回的viewName创建一个View对象   InternalResourceView view = (InternalResourceView) super.buildView(viewName);   if (this.alwaysInclude != null) {      view.setAlwaysInclude(this.alwaysInclude);   }   view.setPreventDispatchLoop(true);   return view;}
  (5)之后就会到该View对象进行视图渲染,然后由Tomcat将数据返回给客户端。 但是如果该url下没有/WEB-INF/pages/liView.jsp文件,就会报错:
 
  2.6Debug源码-自定义View不存在,会走默认视图解析机制 
 视图解析器可以配置多个,按照指定的顺序来对视图进行解析。如果上一个视图解析器不匹配,下一个视图解析器就会去解析视图,以此类推:
 
  
 在容器文件中,将默认的视图解析器调用优先级降低,提高自定义视图解析器的调用优先级。见2.2的容器文件配置
删除2.2中的自定义视图MyView.java。也就是说,自定义视图解析器解析目标方法返回的视图对象时,将会无法解析该视图,因为它不存在。
这时就会去调用下一个优先级的视图解析器,即默认视图解析器。
 debug-03(1)仍然在GoodsHandler中添加断点:
 
  (2)浏览器访问 view.jsp,可以看到后台光标跳转到了断点处: 
  (3)在自定义的视图解析器 BeanNameViewResolver 中打上断点: 
  (4)点击resume,可以看到光标跳转到该断点处: 
  (5)因为在容器文件中找不到该视图对象的id了,因此会进入第一个分支,方法直接返回 null 
  (6)点击step over,光标跳转到中央控制器的 resolveViewName 方法中: 复制代码@Nullableprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model,      Locale locale, HttpServletRequest request) throws Exception {   if (this.viewResolvers != null) {      //循环调用视图解析器,直到某个视图解析器返回的view不为null      for (ViewResolver viewResolver : this.viewResolvers) {         View view = viewResolver.resolveViewName(viewName, locale);         if (view != null) {            return view;         }      }   }   return null;}
 因为自定义视图解析器会返回 null,因此这里进入第二次循环,由默认的视图解析器去进行解析,然后返回对应的视图: 
  (7)在该方法中打上断点,点击 resume,可以看到此时 view 是由默认的视图解析器返回的视图对象,走的是默认机制。 
  (8)下一个就按照默认机制拼接的 url 去访问该页面,并进行渲染。然后由Tomcat返回给客户端。如果根据 url 找不到该页面,就报404错误。 补充:如果默认视图解析器优先级高,自定义的视图解析器优先级低,但是默认视图解析器返回的的View为null,这时候会继续调用自定义的视图解析器吗?
 答:事实上,默认视图解析器返回的 View 不会为 null。
 因为它是根据目标方法返回的字符串+你配置的前后缀进行 url 的拼接。只要目标方法返回了一个字符串,默认视图处理器就不会返回 null。
 
 如果目标方法返回的是 null 呢?将会以目标方法的路径名称+配置的前后缀作为寻找页面的 url因此在循环调用视图处理器的时候,一旦循环到默认视图处理器,就不会调用后面的自定义视图解析器。
 3.目标方法直接指定转发或重定向
 
 3.1使用实例
 
 目标方法中指定转发或者重定向:
 
 
 默认返回的方式是请求转发,然后用视图处理器进行处理。
但是也可以在目标方法中直接指定重定向或者转发的 url 的地址。
注意:如果指定重定向,则不能定向到 /WEB-INF 目录中。因为该目录为 Tomcat 的内部目录。
 例子
 3.2Debug-指定请求转发流程分析
 view.jsp复制代码<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>请求转发或重定向</title></head><body><h1>请求转发或重定向</h1><a target="_blank" href="https://www.cnblogs.com/goods/order">测试在目标方法找中指定请求转发或重定向</a></body></html>
GoodsHandler.java复制代码package com.li.web.viewresolver;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;/** * @author 李 * @version 1.0 */@RequestMapping(value = "/goods")@Controllerpublic class GoodsHandler {    //演示直接指定请求转发或者重定向    @RequestMapping(value = "/order")    public String order() {        System.out.println("=========order()=========");        //请求转发到 /WEB-INF/pages/my_view.jsp        //下面的路径会被解析/web工程路径/WEB-INF/pages/my_view.jsp        return "forward:/WEB-INF/pages/my_view.jsp";    }}
访问 view.jsp,点击超链接:
  
成功进入到请求转发的页面:
  请求转发也可以转发到WEB-INF目录之外的页面。
 
修改 GoodsHandler.java 的 order 方法:复制代码//演示直接指定请求转发或者重定向@RequestMapping(value = "/order")public String order() {    System.out.println("=========order()=========");    //重定向    //1.对于重定向来说,不能重定向到/WEB-INF/目录下    //2.redirect 为重定向的关键字    //3./login.jsp 是在服务器解析的,解析为 /web工程路径/login.jsp    return "redirect:/login.jsp";}
redeployTomcat,访问 view.jsp,点击超链接,可以看到成功重定向到 login.jsp页面
   
 
 
 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
 |