springmvc视图解析器详解

目录

1. 概述

2. ViewResolver和View接口

2.1 ViewResolver接口

2.2 View接口

3. springmvc中如何解析视图

3.1 初始化视图解析器

3.2 解析逻辑视图名

3.3 请求转发与重定向的视图解析

3.4 配置JstlView视图

3.5 产生上面异同的原因

4. 配置thymeleaf视图

5. 使用多种视图

6. 简化返回视图

1. 概述

        文章有点长,建议收藏阅读

        当一个请求被HandlerMapping处理完后,会返回一个ModelAndView对象,springmvc借助视图解析器(ViewResolver)得到最终将逻辑视图解析为视图对象(View),最终的视图可以是jsp,html,也可能是Excel等,转换成对应的View对象后渲染给用户,即返回浏览器,在这个渲染过程中,发挥作用的就是ViewResolver和View两个接口

        对于最终采取哪种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点在生产模型数据上,从而实现mvc的充分解耦,视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户端,主要就是完成转发或重定向操作

2. ViewResolver和View接口

2.1 ViewResolver接口

public interface ViewResolver {
    /**
     * 只有这一个方法,用于把逻辑视图名称解析为真正视图View
     /
   @Nullable
   View resolveViewName(String viewName, Locale locale) throws Exception;

}

视图解析器继承关系:

从上图可以看出,springmvc提供了很多的视图解析器,下面说明一个常用的:

(1)UrlBasedViewResolver(URL资源视图):

主要就是提供的一种拼接URL的方式来解析视图,他可以指定前缀和后缀,然后拼接到返回的逻辑视图名称,就是指定的视图URL了

(2)子类InternalResourceViewResolver:

将视图名解析为一个URL文件,一般使用该解析器将视图名映射为一个保存在WEB-INF下的程序文件如jsp

jsp是常见的视图技术,可以使用InternalResourceViewResolver作为视图解析器:

案例一:

@Controller
public class LoginController {
    @RequestMapping(value = "login")
    public String login() {
        return "login";
    }
}

//在springmvc中没有配置视图解析器的情况下,访问/login,会报错,错误的大概意思就是没有正确设置转发(或包含)到请求调度程序路径
//意思就是把逻辑视图解析后的URL路径还是/login,因为默认情况下该视图解析器解析视图是没有配置前缀和后缀的

 

 抛出异常的就是下面那一行,准备渲染视图时进行的检查抛出的

 现在在springmvc.xml文件中配置InternalResourceViewResolver视图解析器的前缀和后缀

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/"/>
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
</bean>

再次访问/login,得到下图,就能看出区别,两个path是不一样的

2.2 View接口

        不同的视图解析器解析成对应的视图类型

        View接口有很多实现类,接口中有一个重要方法render(),用于渲染给定模型的视图,下面是继承结构,打钩的是常用的视图类型,当然也可以自己实现ViewResolver和View接口,自定义自己的视图解析器

InternalResourceView视图:

        将jsp或者其他资源封装成一个视图,是InternalResourceViewResolver默认使用的视图实现类

文档视图:

        AbstractExcelView: Excel文档视图的抽象类,该视图类基于POI构造excel文档

JSON视图:

        MappingJacksonJsonView: 将数据模型通过Jackson开源框架的ObjectMapper以JSON方式输出

如访问localhost:8080/springmvc/login得到的视图(InternalResourceView)如下:

 

3. springmvc中如何解析视图

3.1 初始化视图解析器

private List<ViewResolver> viewResolvers;  //定义视图解析器集合
private void initViewResolvers(ApplicationContext context) {
    
  	/**
  	 * DispatcherServlet类中通过该方法初始化viewResolvers,如果容器中没有ViewResolver bean,则默认为			  	        * InternalResourceViewResolver
  	 /
  	 
  	 //其他代码不用关心
  	
}

3.2 解析逻辑视图名

也就是依次调用viewResolvers中视图解析器,如果得到了View就返回

3.3 请求转发与重定向的视图解析

请求转发: 返回逻辑视图名包含forward:前缀

重定向: 返回逻辑视图名包含redirect:前缀

默认的: 返回视图名不带任何前缀

此时我们知道默认视图解析器为InternalResourceViewResolver,我们也没有配置其他的

(1)如果是默认的,则解析后是InternalResourceView,如下图

(2)如果是请求转发,则解析后是InternalResourceView,如下图

 

(3)如果重定向,则解析后是RedirectView,如下图

 

3.4 配置JstlView视图

        注意这里是配置视图不是视图解析器,当没有配置其他视图解析器(比如thymeleaf),但配置了InternalResourceViewResolver的viewClass属性

默认的: 就使用viewClass对应的视图,当前是JstlView

请求转发: 则解析后还是InternalResourceView

重定向: 则解析后还是RedirectView

如果导入相关Jstl相关jar包后,默认的视图会自动切换为JstlView,就不用在下面配置viewClass,也会自动生效

 

        JstlView是InternalResourceView的子类,功能比InternalResourceView更强大,比如可以支持快速国际化,或者如果JSP文件中使用了JSTL国际化标签功能,则需要使用该类视图

JstlView视图依赖

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>

3.5 产生上面异同的原因

        比如访问localhost:8080/springmvc/login

        为什么重定向与请求转发,解析后的视图类型不一样,或者添加JstlView依赖后又会产生不一样?

        就需要知道InternalResourceViewResolver如何解析视图的,当前springmvc中只有这一个默认视图解析器

InternalResourceViewResolver继承结构如下:

1.从DispatcherServlet中视图解析入口

 

2.进入resolveViewName()方法

        此时进入到AbstractCachingViewResolver中的resolveViewName()方法,从上面继承结构知道,只有AbstractCachingViewResolver实现了ViewResolver接口中解析方法,具体源码如下:

@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
   if (!isCache()) {
      return createView(viewName, locale);
   }
   else {
       /**
	 	* getCacheKey()实际上就是返回视图名称,在UrlBasedViewResolver中重写的,此时是cacheKey="login"
	 	*/
      Object cacheKey = getCacheKey(viewName, locale);
       
       /**
	 	* 尝试从缓存中获取视图,此时viewAccessCache的size为0,因为是第一次获取视图,缓存中肯定没有的
	 	*  viewAccessCache中key:value比如为:
	 	*  	key:"redirect:login"  value:"RedirectView"
	 	* 	key:"login" 		  value:"InternalResourceView"
	 	*/
      View view = this.viewAccessCache.get(cacheKey);
      if (view == null) {
           /**
	 	    * 如果没有获取到,就在同步块中创建一个视图并返回,并将其缓存,以便下一次需要该视图的时候直接从缓存中返回即可
	 	    */
         synchronized (this.viewCreationCache) {
            view = this.viewCreationCache.get(cacheKey);
            if (view == null) {
                 /**
                  **********************************************************************
	 	          * 重要的就是这个方法,他决定了创建什么类型的视图,但在URLBasedViewResolver被重写了
	 	          **********************************************************************/
               view = createView(viewName, locale);
               if (view == null && this.cacheUnresolved) {
                  view = UNRESOLVED_VIEW;
               }
               if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                 /**
	 	          * 将逻辑视图名与对应视图存入缓存中
	 	          */
                  this.viewAccessCache.put(cacheKey, view);
                  this.viewCreationCache.put(cacheKey, view);
               }
            }
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace(formatKey(cacheKey) + "served from cache");
         }
      }
      return (view != UNRESOLVED_VIEW ? view : null);
   }
}

3. 进入createView():

用来创建一个视图, 具体源码如下:

@Override
protected View createView(String viewName, Locale locale) throws Exception {

   /**
	* 判断一下当前视图解析器能不能处理给定视图,如果不能处理的话,就返回null,交给下一个视图解析器处理
	*/
   if (!canHandle(viewName, locale)) {
      return null;
   }

   /**
	* 检查视图名是否以 "redirect:" 开头
	*/
   if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
       /**
		* 去掉前缀,得到后面的重定向地址
		*/
      String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
       /**
		* 创建一个重定向视图
		*/
      RedirectView view = new RedirectView(redirectUrl,
            isRedirectContextRelative(), isRedirectHttp10Compatible());
      String[] hosts = getRedirectHosts();
      if (hosts != null) {
         view.setHosts(hosts);
      }
      return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
   }

    /**
	 * 检查视图名是否以 "forward:" 开头
	 */
   if (viewName.startsWith(FORWARD_URL_PREFIX)) {
      String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        /**
	 	 * 创建请求转发InternalResourceView视图
	 	 */
      InternalResourceView view = new InternalResourceView(forwardUrl);
      return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
   }

    /**
	 * 如果视图名不以"redirect:"或"forward:"为前缀,则通过父类中的实现创建视图
	 */
   return super.createView(viewName, locale);
}

4.进入super.createView()

从上面继承结构知道,所以回到了AbstractCachingViewResolver中,他的实现如下:

5.进入loadView()

而loadView()这是个抽象方法,在UrlBasedViewResolver中实现,父类又调用子类实现,spring中有很多这种模式

实现如下:

 

6.进入buildView()

        这个方法就在当前类URLBasedViewResolver中的,但是此时视图解析器为InternalResourceViewResolver,在这个子类中又重写了这个方法,下图为InternalResourceViewResolver中的重写,当前实际调用的是这个,而这个实际调用的又是URLBasedViewResolver中的,有点绕,但是跟着debug知道他的设计模式后就会一点一点明白的

 回到URLBasedViewResolver中:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
   Class<?> viewClass = getViewClass(); ;//获取viewClass,需要viewClass
   Assert.state(viewClass != null, "No view class");

   AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
   view.setUrl(getPrefix() + viewName + getSuffix());
   view.setAttributesMap(getAttributesMap());
   //********省略部分
   return view;
}

        最终,我们默认返回的视图类型就在这里创建的,而请求转发和重定向解析的视图类型在上面代码中已经固定

        因为当前项目中添加了Jstl依赖,所以返回默认视图时,解析为JstlView视图,如果不添加Jstl依赖,此时解析为InternalResourcesView视图,这也是为什么配置URLBasedViewResolver解析器时需要配置viewClass,而配置InternalResourcesViewResolver解析器时不用配置viewClass,应该InternalResourcesViewResolver已经设置viewClass默认为InternalResourceView

 

4. 配置thymeleaf视图

1. 导入依赖并配置

<!-- thymeleaf -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.13.RELEASE</version>
</dependency>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                    <!-- 视图前缀 -->
                    <property name="prefix" value="/WEB-INF/"/>
                    <!-- 视图后缀 -->
                    <property name="suffix" value=".html"/>
                    <property name="templateMode" value="HTML5"/>
                    <property name="characterEncoding" value="UTF-8" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

2. 查看springmvc中的解析

直接看图: 此时就只有thymeleaf解析器了,此时

默认的: 解析为ThymeleafView

请求转发: 则解析后还是InternalResourceView

重定向: 则解析后还是RedirectView

再看一张图:

        thymeleaf解析器继承了AbstractCachingViewResolver,从上面知道,AbstractCachingViewResolver解析视图类型时先从缓存中找,缓存中没有的话就调用createView()方法来创建一个

 

而此时thymeleaf重写了该方法,源码如下: 

5. 使用多种视图

        我们可以选用一种视图解析器或混用多种视图解析器,每个视图解析器都实现Ordered接口并开放出一个order属性,springmvc会按照视图解析器顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出异常

<!-- 配置InternalResourceViewResolver视图解析器 -->
<bean id="viewResolver2" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/"/>
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
    <property name="order" value="2"/>
</bean>

<!-- 配置thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 视图前缀 -->
                        <property name="prefix" value="/WEB-INF/"/>
                        <!-- 视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

配置的order越小,优先级越高,当前thmeleaf视图解析器在前,如下图:

6. 简化返回视图

        当控制器只是返回视图时,可以在springmvc配置文件中配置来简化,并且需要加上开启注解驱动,否则造成@Controller注解无法解析,造成404错误

<!--此时直接访问localhost:8080/springmvc/login就可以获取名为login的逻辑视图 -->
<mvc:view-controller path="/login" view-name="login"></mvc:view-controller>
<mvc:annotation-driven/>

  • 11
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值