目录
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. 简化返回视图
文章有点长,建议收藏阅读
当一个请求被HandlerMapping处理完后,会返回一个ModelAndView对象,springmvc借助视图解析器(ViewResolver)得到最终将逻辑视图解析为视图对象(View),最终的视图可以是jsp,html,也可能是Excel等,转换成对应的View对象后渲染给用户,即返回浏览器,在这个渲染过程中,发挥作用的就是ViewResolver和View两个接口
对于最终采取哪种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点在生产模型数据上,从而实现mvc的充分解耦,视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户端,主要就是完成转发或重定向操作
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视图解析器的前缀和后缀
再次访问/login,得到下图,就能看出区别,两个path是不一样的
不同的视图解析器解析成对应的视图类型
View接口有很多实现类,接口中有一个重要方法render(),用于渲染给定模型的视图,下面是继承结构,打钩的是常用的视图类型,当然也可以自己实现ViewResolver和View接口,自定义自己的视图解析器
InternalResourceView视图:
将jsp或者其他资源封装成一个视图,是InternalResourceViewResolver默认使用的视图实现类
文档视图:
AbstractExcelView: Excel文档视图的抽象类,该视图类基于POI构造excel文档
JSON视图:
MappingJacksonJsonView: 将数据模型通过Jackson开源框架的ObjectMapper以JSON方式输出
如访问localhost:8080/springmvc/login得到的视图(InternalResourceView)如下:
private List viewResolvers; //定义视图解析器集合
private void initViewResolvers(ApplicationContext context) {
/**
* DispatcherServlet类中通过该方法初始化viewResolvers,如果容器中没有ViewResolver bean,则默认为 * InternalResourceViewResolver
/
//其他代码不用关心
}
也就是依次调用viewResolvers中视图解析器,如果得到了View就返回
请求转发: 返回逻辑视图名包含forward:前缀
重定向: 返回逻辑视图名包含redirect:前缀
默认的: 返回视图名不带任何前缀
此时我们知道默认视图解析器为InternalResourceViewResolver,我们也没有配置其他的
(1)如果是默认的,则解析后是InternalResourceView,如下图
(2)如果是请求转发,则解析后是InternalResourceView,如下图
(3)如果重定向,则解析后是RedirectView,如下图
注意这里是配置视图不是视图解析器,当没有配置其他视图解析器(比如thymeleaf),但配置了InternalResourceViewResolver的viewClass属性
默认的: 就使用viewClass对应的视图,当前是JstlView
请求转发: 则解析后还是InternalResourceView
重定向: 则解析后还是RedirectView
如果导入相关Jstl相关jar包后,默认的视图会自动切换为JstlView,就不用在下面配置viewClass,也会自动生效
JstlView是InternalResourceView的子类,功能比InternalResourceView更强大,比如可以支持快速国际化,或者如果JSP文件中使用了JSTL国际化标签功能,则需要使用该类视图
JstlView视图依赖
javax.servlet
jstl
1.2
比如访问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
1. 导入依赖并配置
org.thymeleaf
thymeleaf-spring5
3.0.13.RELEASE
2. 查看springmvc中的解析
直接看图: 此时就只有thymeleaf解析器了,此时
默认的: 解析为ThymeleafView
请求转发: 则解析后还是InternalResourceView
重定向: 则解析后还是RedirectView
再看一张图:
thymeleaf解析器继承了AbstractCachingViewResolver,从上面知道,AbstractCachingViewResolver解析视图类型时先从缓存中找,缓存中没有的话就调用createView()方法来创建一个
而此时thymeleaf重写了该方法,源码如下:
我们可以选用一种视图解析器或混用多种视图解析器,每个视图解析器都实现Ordered接口并开放出一个order属性,springmvc会按照视图解析器顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出异常
配置的order越小,优先级越高,当前thmeleaf视图解析器在前,如下图:
当控制器只是返回视图时,可以在springmvc配置文件中配置来简化,并且需要加上开启注解驱动,否则造成@Controller注解无法解析,造成404错误