- 什么是ViewResolver?
从接口的定义来看,ViewResolver就是专门用来生成View的,如下:public interface ViewResolver { /** * viewName就是从controller返回的要跳转到页面的字符串 **/ View resolveViewName(String viewName, Locale locale) throws Exception; }
- ViewResolver有哪些?
下面来看一下springmvc给我们默认自带了那些ViewResolver,如下图:
从上图可以看出,有好多ViewResolver,也就是说每个ViewResolver都会生成一个不同View(View是什么?看我对应的文章)。那么到底是用哪个ViewResolver呢?根据实际情况,比如,如果我们用的是jsp页面,那么我们就需要使用InternalResourceViewResolver来生成对应的View(JstlView,只有这个View,才能跟jsp页面一同配合使用)。
- 在什么地方用到ViewResolver?
如果你是从【DispatcherServlet源码解析】这篇文章过来的,那么肯定知道,在DispatcherServlet的initViewResolvers方法中,会从容器中获取ViewResolver的实现类,如下: - 如何把ViewResolver放到容器中?
上面说了ViewResolver是在DispatcherServlet初始化时,会从容器中获取ViewResolver,那么如何把ViewResolver中呢?很简单,方法之一就是,在spring-mvc.xml中(这个文件的名字是会变化的,反正就是一个配合springmvc的xml文件)配置即可,如下:
ViewResolver详解
上面我们通过图片展示了springmvc为我们自带了很多ViewResolver的实现类,那么现在我们就来看一下每一个的作用和具体能生成什么View
1. AbstractCachingViewResolver
这个抽象类实现了ViewResolver,也是很多ViewResolver实现类的基类。把它作为抽象类然后让其他实现类继承,主要是为了缓存已有的view,然后提高性能。先来看下面一个请求发起后的执行流程:
看到最后一步的resolveViewName方法了吧,这个时候便会调用AbstractCachingViewResolver这个基类中的resolveViewName方法,然后在这里面再去调用各个子类具体生成View的方法。为什么这么说呢,看一下他源码:
@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) {
//② 这里就是根据多态原理,去调用子类的createView方法创建View
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
// ③
// 这个基类的唯一所用就是在这,作为一个缓存,缓存住已经没有的View
// 这样下次浏览器在请求时,可以直接从缓存获取这个View
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
注意看源码①,②,③这三个位置,这个类的核心就三处。其中①和③主要是从缓存中查找view和把view放入缓存。②用来调用子类来生成View。
2. UrlBasedViewResolver
这个类的主要作用是通过前缀和后缀设置指定文件的位置和扩展名的。比如一个位于WEB-INF/jsp下的hello.jsp,他们只用这个类的属性prefix来设置hello.jsp的位置(WEB-INF/jsp),通过suffix指定hello.jsp的扩展名.jsp,然后controller返回hello字符串后,此类会把他们拼在一起组成一个url,然后通过这个url就可以定位到文件位置了(WEB-INF/jsp/hello.jsp)。所有当要使用jsp,freemark等模板是,就可以使用此类来设置文件位置。
这个类是AbstractCachingViewResolver的子类,我们来看下官网对这个类的说明:
Simple implementation of the org.springframework.web.servlet.ViewResolver interface, allowing for direct resolution of symbolic view names to URLs, without explicit mapping definition without explicit mapping definition。 Example: prefix="/WEB-INF/jsp/", suffix=".jsp", viewname="test" -> "/WEB-INF/jsp/test.jsp" Supports 意思就是说:它是ViewResolver 的实现类,他可以直接通过nameview与视图文件(jsp,freemark等文件)的位置进行映射,而无需显式的进行映射配置(类似于web.xml中servlet那样为显式的进行映射配置)。 例如:通过提前设置好prefix="/WEB-INF/jsp/", suffix=".jsp",然后通过viewname="test" ,就可以映射到"/WEB-INF/jsp/test.jsp"。 他的子类 InternalResourceView ,VelocityView and FreeMarkerView也都具有同样的特性。 |
具体的意思,就是说通过提前设定好了jsp文件所在目录,当controller方法执行完后,便用方法的返回值(字符串)作为viewname,然后用这个viewname与之前设定好的jsp文件的位置拼接起来,就可以直接定位到jsp文件了,下面用一个例子来具体说明下:
假如下面是我的jsp文件的存放位置 我的controller如下:
我提前设置好jsp文件的路径为/WEB-INF/jsp/,文件的扩展名为.jsp。这时当controller返回hello后,便会通过UrlBasedViewResolver中的方法,将/WEB-INF/jsp/ 和hello和.jsp拼接在一起(/WEB-INF/jsp/hello.jsp),这样再看,是不是拼接在一起后的值/WEB-INF/jsp/hello.jsp,正好就是我们存放jsp文件中的那个hello.jsp文件的路径地址了。这就实现了映射。
这样当controller返回hello后,便会显示hello.jsp这个文件了(开头变说过,ViewResolver是用来生成View的,所以拼接的这个地址是在生成View的时候,设置给View的。本段的文字,只是为了让大家容易理解,而忽略了内部其他的细节)。
那么这个提前设置的目录和扩展名(.jsp),是在哪设置的呢?我贴个图,你们就一目了然了:
为什么这里设置的name叫做prefix和suffix呢,因为prefix和suffix是UrlBasedViewResolver中的属性。关于为什么这么写就能给属性赋值,我就不讲了,这属于spring基础了。就是在声明bean的时候,直接当做属性设置就行了。
这里要注意的是,我们声明bean的时候,必须使用UrlBasedViewResolver的子类,因为我们说过ViewResolver是用来生成View的,具体这个View是什么(View的具体是那个类),这个是在UrlBasedViewResolver的子类中生成的,每个子类生成的View是不同的。
UrlBasedViewResolver主要负责的是将/WEB-INF/jsp/ 和hello和.jsp拼接在一起(/WEB-INF/jsp/hello.jsp),然后通过多态的的特点,整合子类创建的属性,然后创建出一个View。源码如下:
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView)
BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
//...省略其他代码...
return view;
}
也就是说,要创建一个什么样的view(具体view的class),由UrlBasedViewResolver的子类来决定,并赋值到UrlBasedViewResolver的属性viewClass上,然后,最终由UrlBasedViewResolver来使用这个viewClass通过反射来创建这个View。然后再把拼接好的url(/WEB-INF/jsp/hello.jsp)赋值给这个View。这个类的作用其实就这么简单。下面我们再来看看他的子类
另外这个类中还指明:
如果你的viewname以【redirect:】开头,那么会创建一个重定向的RedirectView。
如果你的viewname以【forward:】开头,那么会创建一个请求转发的InternalResourceView。
这也就是为什么我们经常在controller的方法中会看到例如 【return redirect:***】或者【return forward:***】的写法的原因了。
- InternalResourceViewResolver
InternalResourceViewResolver是UrlBasedViewResolver的子类,先来看下官方的说明:
Convenient subclass of
UrlBasedViewResolver
that supportsInternalResourceView
(i.e. Servlets and JSPs) and subclasses such asJstlView
.The view class for all views generated by this resolver can be specified via
setViewClass
. SeeUrlBasedViewResolver
's javadoc for details. The default isInternalResourceView
, orJstlView
if the JSTL API is present.BTW, it's good practice to put JSP files that just serve as views under WEB-INF, to hide them from direct access (e.g. via a manually entered URL). Only controllers will be able to access them then.
Note: When chaining ViewResolvers, an InternalResourceViewResolver always needs to be last, as it will attempt to resolve any view name, no matter whether the underlying resource actually exists.
意思就是说:
它是UrlBasedViewResolver
的子类,用来生成InternalResourceView
或者JstlView,生成view后,通过调用父类的setViewClass方法赋值给父类
UrlBasedViewResolver的viewClass属性。InternalResourceViewResolver默认生成的是InternalResourceView,如果上下文中找到jstl api,那就生成JstlView
顺便一提,jsp文件最好放到WEB-INF下,这样就可以防止通过浏览器url直接访问我们的jsp文件,而只能通过controller去访问jsp文件。
注意:当有多个ViewResolvers时,InternalResourceViewResolver 必须放在最后一个,因为他会直接去尝试根据viewname去找对应的文件,而不关心文件是否存在。从官网说明可以看出:InternalResourceViewResolver主要是用来生成与Jsp搭配使用的View---
InternalResourceView
和JstlView
所谓专门的人做专门的事,如果你的项目中使用的是jsp,那么你就必须使用这个InternalResourceViewResolver。
下面写一个例子:@Controller @RequestMapping("/view") public class ViewController { @RequestMapping("/hellojsp") public String toHelloJsp(){ return "hello"; } }
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="order" value="10"></property> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> <!-- <property name="viewClass" ref="jstViewClass"></property> --> </bean>
jsp文件目录: 浏览器请求http://localhost:8088/view/hellojsp
- AbstractTemplateViewResolver
AbstractTemplateViewResolver是UrlBasedViewResolver的子类,看下官方说明:
Abstract base class for template view resolvers,in particular for Velocity and FreeMarker views.
意思是:它是一个抽象类,主要用来生成模板view,比如Velocity和FreeMarker。这个类是一个抽象类,那么自然不能实例化,所以要生成Velocity或者FreeMarker这些View,就自然而然需要相对应的子类来完成。所以我们下面直接来看他的子类。
- FreeMarkerViewResolver
AbstractTemplateViewResolver的子类,如果想使用freemark这样的模板作为view来作为请求的内容展示,那么就必须使用这个resolver来生成对应的FreeMarker的View。也就是说如果你的页面想使用FreeMarker模板来生成,那么你就需要声明一个FreeMarkerViewResolver。为什么必须使用FreeMarkerViewResolver才能来生成FreeMark的View呢,看一下源码:
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver { public FreeMarkerViewResolver() { setViewClass(requiredViewClass()); } public FreeMarkerViewResolver(String prefix, String suffix) { this(); setPrefix(prefix); setSuffix(suffix); } @Override protected Class<?> requiredViewClass() { return FreeMarkerView.class; } }
看到requiredViewClass方法了吧,这里生成了FreeMarkerView(前面说过,不同的resolver生成不同的View,就体现在这个方法上)。
下面通过一个使用FreeMarker显示index页面的例子来说明一下如果使用:
第一步:pom.xml中引入FreeMarker的jar
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency>
第二步:配置spring-mvc.xml,引入resolver,同时必须引入FreeMarkerConfigurer,否则会报异常
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath"> <value>/WEB-INF/flt/</value><!--所有freemark文件放到flt目录下--> </property> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="order" value="10"></property> <property name="prefix" value=""></property> <property name="suffix" value=".ftl"></property> </bean>
前面说过FreeMarkerViewResolver也是UrlBasedViewResolver的子类,所有用法跟上面的jsp的配置一样,只要配置prefix和suffix就行。原理上面已经说过了。
第三步:写Controller@Controller @RequestMapping("/view") public class ViewController { @RequestMapping("/freemark") public String toFreemark(){ return "index"; } }
第四步:写FreeMarker模板文件index.ftl:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- <meta http-equiv="refresh" content="0; url=hello.ftl" /> --> <title>Insert title here</title> </head> <body> <a href="helloFtl">hello</a> </body> </html>
第五步:测试,浏览器http://localhost:8087/view/freemark
-
GroovyMarkupViewResolver
这个Resolver同样是UrlBasedViewResolver的子类,所有用法跟上面的jsp的配置一样,因为我们很少使用,所以这里就做实例样式了,可自行百度 -
VelocityViewResolver
这个Resolver同样是UrlBasedViewResolver的子类,所有用法跟上面的jsp的配置一样,因为我们很少使用,所以这里就做实例样式了,可自行百度,并且官方已经经此类弃用了,如下图,所以我们也不写例子,主要指导器原理就行。 -
JasperReportsViewResolver
JasperReportsViewResolver主要使用来生成JasperReportsView的,关于这个view,可以看一下下面的图。这里先来稍微数一下什么是JasperReports,他就是一个专门用来生成报表的东东。看上图,这个生成的报表又可以以多种形式来展示,比如有html形式的 ,pdf形式的,xls形式的等等。一会我们会通过一个html的小例子来演示一下。
因为这个类同样是UrlBasedViewResolver的子类,所以原理都是相同的。【例子有时间会填充在这】 -
ScriptTemplateViewResolver
这个类要与ScriptTemplateConfigurer一起使用。【例子有时间会填充在这】 -
TilesViewResolver
-
XsltViewResolver
XmlViewResolver
通过单独配置的xml文件,来将显示的页面和controller中返回的model的名字进行映射方式来显示页面。例子如下:
spring-mvc.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <mvc:annotation-driven /> <context:component-scan base-package="com.lhb"> </context:component-scan> <!-- 静态资源 --> <mvc:resources mapping="/style/**" location="/style/" /> <mvc:resources mapping="/images/**" location="file:d:/images/" /> <!--注册bean --> <bean name="/bean/name/mapping/hello" class="com.lhb.controller.BeanNameURLHandlerMappingController"></bean> <bean class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="location"> <!--指定xmlViewResolver配置映射页面和controller返回的model名的位置,也就是通过下面指定的这个xml文件,来映射controller返回的model名对应的显示的页面--> <value>classpath:spring-views.xml</value><!--默认是/WEB-INF/views.xml.--> </property> </bean> </beans>
注意:当使用了XmlViewResolver时,别忘了删除我们常用的设置jsp的那个InternalResourceViewResolver
spring-views.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--id的值hello对应的就是controller中model的名字--> <bean id="hello" class="org.springframework.web.servlet.view.JstlView"> <property name="url" value="/WEB-INF/jsp/hello.jsp" /> </bean> </beans>
BeanNameURLHandlerMappingController.java
public class BeanNameURLHandlerMappingController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { // 注意这个参数hello对于与spring-view.xml中bean的id ModelAndView model = new ModelAndView("hello"); model.addObject("name", "kitty"); model.addObject("age", "1"); model.addObject("message", "Hello World!"); return model; } }
hello.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> 你好,<span>${user}</span><br/> 您的年龄为:<span>${age}</span> </body> </html>
测试:
解析过程:当控制器返回一个名为“hello”的视图时,XmlViewResolver将在“spring-views.xml”文件中查找id为“hello”的bean,并返回相对应的视图URL“/WEB-INF/jsp/hello.jsp”给DispatcherServlet。
ResourceBundleViewResolver
先来看ResourceBundle这俩个单词,一看这两个单词就要与spring的properties文件联系起来。ResourceBundleViewResolver就是指通过properties来配置view的(也就是用什么在页面上显示内容)。
下面先来看看官网api的对于这个类的说明A
org.springframework.web.servlet.ViewResolver
implementation that uses bean definitions in aResourceBundle
, specified by the bundle basename.The bundle is typically defined in a properties file, located in the classpath. The default bundle basename is "views".
意思就是说:ResourceBundleViewResolver的核心是ResourceBundle这个类(暂时可以认为这个类就是从properties文件中获取数据)【1】。然后通过basename来指定ResourceBundle从哪个properties文件中读取内容【2】。默认是使用classpath中views.proerties文件。【3】
【1】
【2】
【3】
下面通过例子来说明一下:
spring-mvc.xml<bean id="myViewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="order" value="1"/> <property name="basename" value="views"/> </bean>
views.properties
hello1.(class)=org.springframework.web.servlet.view.JstlView hello1.url=/WEB-INF/jsp/hello.jsp #b.class=org.springframework.web.servlet.view.JstlView #b.url=/WEB-INF/jsp/b.jsp
viewController.java
package com.lhb.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/view") public class ViewController { @RequestMapping("/hellojsp") public String toHelloJsp() { return "hello1"; } }
hello.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> 你好<br/> 您的年龄为: </body> </html>
hello.jsp的文件位置
测试:
总结:
通过一个简单例子后,我们再回头看一下什么是ResourceBundleViewResolver。说白了就是:通过一个properties文件来指定使用什么view技术在页面上显示,并且controller中返回的viewname值与显示结果的页面映射起来(比如例子中return “hello1”这就是返回的viewname,或者创建modelAndView时,构造方法中指定的名字)。
注意:这种方式不能再使用@Responsebody,因为用了这个注解,方法返回的值代表的不再是view了,而是直接将字符串显示在页面了。