0%

ViewResolver

Spring MVC ViewResolver 详解:视图解析器的原理与实战

ViewResolver(视图解析器)是 Spring MVC 中连接 “逻辑视图名” 与 “物理视图” 的核心组件,其核心职责是 将 Controller 方法返回的逻辑视图名(如 "user/list")解析为具体的 View 对象(如 JSP、FreeMarker 模板),并触发视图渲染,最终将业务数据以页面形式呈现给用户。从 “核心原理→实现类解析→配置实战→渲染流程” 四个维度,彻底讲透 ViewResolver 的工作机制。

ViewResolver 核心定义与作用

ViewResolver 是一个接口,定义了视图解析的通用行为,所有视图解析器都需实现该接口,核心是 “逻辑视图名→View 对象” 的转换。

1. 核心接口方法

1
2
3
4
5
6
7
8
9
10
public interface ViewResolver {
/**
* 根据逻辑视图名和Locale,解析为具体的View对象
* @param viewName 逻辑视图名(如 Controller 返回的 "user/list")
* @param locale 语言区域(用于国际化视图,如中文/英文页面)
* @return View 对象(如 InternalResourceView 对应 JSP);null:无法解析该视图名
* @throws Exception 解析过程中发生异常(如视图文件不存在)
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}
  • 逻辑视图名:Controller 方法返回的字符串(如 return "user/list")或 ModelAndView 中的视图名,不包含物理路径和后缀;
  • View 对象:封装了视图的渲染逻辑(如 JSP 渲染、FreeMarker 模板填充),核心方法是 render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response),负责将 Model 数据填充到视图模板中。

2. 核心流程:从逻辑视图到页面渲染

ViewResolver 的工作流程可概括为 3 步:

  1. 接收逻辑视图名:DispatcherServlet 从 ModelAndView 中获取逻辑视图名(如 "user/list");
  2. 解析为物理视图路径:ViewResolver 根据配置的 “前缀 + 逻辑视图名 + 后缀” 生成物理路径(如 /WEB-INF/jsp/user/list.jsp);
  3. 创建 View 对象并渲染:根据物理路径创建对应的 View 对象(如 JstlView),调用其 render() 方法,将 Model 数据填充到视图模板,生成 HTML 响应。

ViewResolver 主要实现类解析

Spring MVC 提供了多个 ViewResolver 实现,支持不同类型的视图技术(如 JSP、FreeMarker、PDF),以下是核心实现类的对比与使用场景:

实现类 核心功能 适用场景 核心配置属性 优缺点
AbstractCachingViewResolver 抽象类,提供视图缓存功能(避免重复解析) 所有视图解析器的父类,无直接使用场景 cache(是否缓存,默认 true) 提升性能;开发阶段需关闭缓存(避免修改不生效)
XmlViewResolver 从 XML 文件(默认 /WEB-INF/views.xml)加载视图配置 需集中管理大量视图的场景 location(XML 配置文件路径) 配置集中;XML 维护成本高
ResourceBundleViewResolver 从属性文件(如 views.properties)加载视图配置 多语言、多环境视图切换场景 basename(属性文件基础名) 支持国际化;不支持动态路径
UrlBasedViewResolver 直接将逻辑视图名作为 URL 路径(无前缀后缀) 视图路径与逻辑视图名完全一致的场景 viewClass(View 实现类) 配置简单;灵活性低
InternalResourceViewResolver 继承 UrlBasedViewResolver,支持 JSP 等内部资源视图 传统 JSP 项目(主流) prefix(前缀)、suffix(后缀) 配置灵活;仅支持内部资源(如 JSP)
FreeMarkerViewResolver 继承 UrlBasedViewResolver,支持 FreeMarker 模板 静态页面、模板化项目 prefixsuffixviewClass(FreeMarkerView) 模板复用性高;需额外配置 FreeMarker 环境

1. 最常用实现:InternalResourceViewResolver

InternalResourceViewResolver 是 Spring MVC 中最常用的视图解析器,专门用于解析 JSP 视图,通过 “前缀 + 逻辑视图名 + 后缀” 的方式生成物理路径,是传统视图驱动项目的首选。

(1)核心原理
  • 逻辑视图名→物理路径:例如配置 prefix="/WEB-INF/jsp/"suffix=".jsp",逻辑视图名 "user/list" 会被解析为 /WEB-INF/jsp/user/list.jsp
  • View 类型:默认使用 InternalResourceView(普通 JSP),若需支持 JSTL 标签(如 c:forEach),需指定 viewClassJstlView
(2)XML 配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 配置 InternalResourceViewResolver(JSP 视图解析器) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 可选:指定 View 实现类(支持 JSTL 标签需配置 JstlView) -->
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>

<!-- 视图路径前缀:逻辑视图名前添加的路径 -->
<property name="prefix" value="/WEB-INF/jsp/"/>

<!-- 视图路径后缀:逻辑视图名后添加的后缀 -->
<property name="suffix" value=".jsp"/>

<!-- 可选:视图缓存(开发阶段建议关闭,生产阶段开启) -->
<property name="cache" value="false"/>

<!-- 可选:优先级(多个 ViewResolver 时,值越小优先级越高) -->
<property name="order" value="1"/>
</bean>
(3)Java 配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
public class ViewConfig {
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class); // 支持 JSTL
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
resolver.setCache(false); // 开发阶段关闭缓存
return resolver;
}
}
(4)实战使用

Controller 方法返回逻辑视图名,ViewResolver 自动解析为 JSP 路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class UserController {
@GetMapping("/user/list")
public String getUserList(Model model) {
// 业务逻辑:查询用户列表
model.addAttribute("userList", Arrays.asList("张三", "李四"));

// 返回逻辑视图名:"user/list" → 解析为 /WEB-INF/jsp/user/list.jsp
return "user/list";
}
}

JSP 视图(/WEB-INF/jsp/user/list.jsp)通过 EL 表达式获取 Model 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<h1>用户列表</h1>
<ul>
<!-- 遍历 Model 中的 userList 数据 -->
<c:forEach items="${userList}" var="user">
<li>${user}</li>
</c:forEach>
</ul>
</body>
</html>

2. 模板视图解析器:FreeMarkerViewResolver

FreeMarkerViewResolver 用于解析 FreeMarker 模板(.ftl 文件),适合需要模板复用、静态页面生成的场景(如电商商品详情页)。

(1)依赖配置

需添加 FreeMarker 依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.20</version>
</dependency>
(2)XML 配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 1. 配置 FreeMarker 配置工厂 -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- FreeMarker 模板文件路径(classpath 下的 templates 目录) -->
<property name="templateLoaderPath" value="classpath:/templates/"/>
<!-- FreeMarker 全局配置 -->
<property name="freemarkerSettings">
<props>
<prop key="default_encoding">UTF-8</prop> <!-- 编码 -->
<prop key="template_update_delay">0</prop> <!-- 模板更新延迟(开发阶段 0,即实时更新) -->
</props>
</property>
</bean>

<!-- 2. 配置 FreeMarkerViewResolver -->
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
<property name="prefix" value="user/"/> <!-- 模板文件前缀(templates/user/) -->
<property name="suffix" value=".ftl"/> <!-- 模板文件后缀 -->
<property name="contentType" value="text/html;charset=UTF-8"/> <!-- 响应类型 -->
<property name="order" value="0"/> <!-- 优先级高于 InternalResourceViewResolver -->
</bean>
(3)实战使用

Controller 返回逻辑视图名 "list",解析为 classpath:/templates/user/list.ftl

1
2
3
4
5
@GetMapping("/user/freemarker/list")
public String getFreeMarkerUserList(Model model) {
model.addAttribute("userList", Arrays.asList("张三", "李四"));
return "list"; // 逻辑视图名 → 解析为 user/list.ftl
}

FreeMarker 模板(templates/user/list.ftl):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FreeMarker 用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<ul>
<!-- FreeMarker 遍历语法 -->
<#list userList as user>
<li>${user}</li>
</#list>
</ul>
</body>
</html>

3. 抽象基类:AbstractCachingViewResolver

AbstractCachingViewResolver 是所有视图解析器的抽象父类,提供 视图缓存功能—— 首次解析视图后,将 View 对象缓存到内存,后续相同逻辑视图名直接从缓存获取,避免重复解析,提升性能。

核心配置属性
  • cache:是否开启缓存(默认 true,生产阶段推荐开启;开发阶段需设为 false,避免模板修改后不生效);
  • cacheLimit:缓存视图的最大数量(默认无限制,可防止内存溢出)。
示例:关闭开发阶段缓存
1
2
3
4
5
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
<property name="cache" value="false"/> <!-- 开发阶段关闭缓存 -->
</bean>

多 ViewResolver 共存与优先级

当 Spring 容器中存在多个 ViewResolver 时(如同时支持 JSP 和 FreeMarker),DispatcherServlet 会按以下规则选择:

  1. order 属性排序order 值越小,优先级越高(Ordered 接口);
  2. 逐个尝试解析:按优先级顺序调用 resolveViewName(),第一个返回非 null View 对象的解析器生效;
  3. 未找到解析器:抛出 ServletException(如 “Could not resolve view with name ‘xxx’”)。
示例:多 ViewResolver 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 1. FreeMarkerViewResolver(优先级 0,高) -->
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="prefix" value="user/"/>
<property name="suffix" value=".ftl"/>
<property name="order" value="0"/>
</bean>

<!-- 2. InternalResourceViewResolver(优先级 1,低) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="1"/>
</bean>
  • 若逻辑视图名 "list" 能被 FreeMarkerViewResolver 解析(存在 user/list.ftl),则优先使用 FreeMarker 模板;
  • 若 FreeMarker 模板不存在,再尝试 InternalResourceViewResolver 解析为 WEB-INF/jsp/list.jsp

View 渲染流程(源码简化)

ViewResolver 解析出 View 对象后,DispatcherServlet 会调用 View 的 render() 方法完成渲染,核心流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// DispatcherServlet#render 方法(简化)
private void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 获取请求的 Locale(用于国际化视图)
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);

View view;
String viewName = mv.getViewName();
if (viewName != null) {
// 2. 调用 ViewResolver 解析逻辑视图名,获取 View 对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + viewName + "'");
}
} else {
// 3. ModelAndView 已直接包含 View 对象,无需解析
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object");
}
}

// 4. 调用 View 的 render 方法,渲染视图
try {
view.render(mv.getModelInternal(), request, response);
} catch (Exception ex) {
throw ex;
}
}

// View#render 方法(以 InternalResourceView 为例)
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 将 Model 数据存入 request 域(JSP 需通过 request.getAttribute 获取)
exposeModelAsRequestAttributes(model, request);

// 2. 解析物理视图路径(如 /WEB-INF/jsp/user/list.jsp)
String dispatcherPath = prepareForRendering(request, response);

// 3. 转发请求到 JSP 页面(通过 RequestDispatcher)
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + dispatcherPath + "]");
}

// 4. 转发请求(JSP 引擎解析 JSP,填充数据后生成 HTML)
rd.forward(request, response);
}
核心步骤拆解:
  1. Model 数据暴露:将 Model 中的键值对存入 request 域(如 request.setAttribute("userList", userList)),确保 JSP / 模板能通过 EL 表达式或模板语法获取;
  2. 物理路径解析:根据 ViewResolver 配置生成最终的视图路径(如 JSP 路径、FreeMarker 模板路径);
  3. 请求转发 / 渲染:
    • JSP 视图:通过 RequestDispatcher.forward() 转发到 JSP 页面,JSP 引擎解析标签和 EL 表达式,生成 HTML;
    • FreeMarker 视图:FreeMarker 引擎加载模板文件,填充 Model 数据,生成 HTML 并写入响应流。

常见问题与解决方案

1. 开发阶段视图修改不生效

  • 原因:ViewResolver 开启了缓存(默认 cache=true),修改视图文件后,系统仍使用缓存的 View 对象;

  • 解决方案:开发阶段将cache设为false:

    1
    <property name="cache" value="false"/>

2. 逻辑视图名解析为 404 错误

  • 原因:
    1. prefix/suffix 配置错误(如路径拼写错误、缺少 /);
    2. 视图文件不存在(如 JSP 未放在 /WEB-INF/jsp/ 目录);
    3. 多 ViewResolver 优先级配置错误,未找到正确的解析器;
  • 解决方案:
    1. 检查 prefix/suffix 拼接后的物理路径是否正确(如 "user/list"/WEB-INF/jsp/user/list.jsp);
    2. 确认视图文件路径与解析后的路径一致;
    3. 调整 order 属性,确保目标 ViewResolver 优先执行。

3. JSTL 标签无法使用

  • 原因:未指定 viewClassJstlView,默认的 InternalResourceView 不支持 JSTL 标签;

  • 解决方案:配置viewClass:

    1
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>

    同时添加 JSTL 依赖:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    </dependency>

总结与最佳实践

1. 核心总结

  • ViewResolver 作用:将逻辑视图名解析为物理视图,是 Model 数据与视图模板的桥梁;
  • 常用实现:JSP 项目用 InternalResourceViewResolver,模板项目用 FreeMarkerViewResolver
  • 多解析器共存:通过 order 属性控制优先级,满足多种视图技术的需求;
  • 缓存策略:生产阶段开启缓存提升性能,开发阶段关闭缓存确保修改实时生效。

2. 最佳实践建议

  1. 视图路径规范:
    • JSP 文件放在 /WEB-INF/jsp/ 目录(避免直接访问);
    • 模板文件(如 FreeMarker)放在 classpath:/templates/ 目录,便于打包和部署。
  2. 开发与生产环境区分:
    • 开发阶段:关闭视图缓存(cache=false),开启模板实时更新;
    • 生产阶段:开启视图缓存(cache=true),设置合理的缓存数量(cacheLimit)。
  3. 前后端分离项目:
    • 若项目为前后端分离(仅返回 JSON,无视图渲染),可省略 ViewResolver 配置,通过 @ResponseBody 直接返回数据;
    • 若需返回静态页面(如 Vue/React 打包后的 HTML),可使用 UrlBasedViewResolver 直接映射静态资源路径

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10