springmvc视图解析器详解

  • Post author:
  • Post category:其他



目录


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/>



版权声明:本文为Eaeyson原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。