👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
18-SpringMVC视图解析与模板引擎
📖 本文概述
本文是SSM框架系列SpringMVC进阶篇的第一篇,将深入探讨SpringMVC的视图解析机制和各种模板引擎的集成使用。通过详细的配置示例和最佳实践,帮助读者掌握视图层技术的选择和使用。
🎯 学习目标
- 深入理解SpringMVC的视图解析原理
- 掌握各种视图解析器的配置和使用
- 学会集成和使用不同的模板引擎
- 了解视图技术的选择标准
- 掌握视图层的最佳实践
1. 视图解析原理
1.1 视图解析流程
/**
* SpringMVC视图解析流程演示
*/
public class ViewResolutionProcess {
/**
* 视图解析的完整流程
*/
public void viewResolutionFlow() {
/*
* 1. Controller返回视图名称
* @GetMapping("/user/list")
* public String userList() {
* return "user/list"; // 返回逻辑视图名
* }
*/
/*
* 2. DispatcherServlet获取视图名称
* String viewName = "user/list";
*/
/*
* 3. ViewResolver解析视图名称
* ViewResolver根据配置将逻辑视图名转换为具体的View对象
* 例如:InternalResourceViewResolver
* "user/list" -> "/WEB-INF/views/user/list.jsp"
*/
/*
* 4. 创建View对象
* View view = new JstlView("/WEB-INF/views/user/list.jsp");
*/
/*
* 5. View渲染
* view.render(model, request, response);
* - 将Model数据设置为request属性
* - 转发到JSP页面进行渲染
* - 生成HTML响应
*/
/*
* 6. 返回响应给客户端
* 浏览器接收到渲染后的HTML页面
*/
}
}
1.2 视图解析器架构
/**
* 视图解析器架构演示
*/
public class ViewResolverArchitecture {
/**
* ViewResolver接口
*/
public interface ViewResolver {
/**
* 根据视图名称和Locale解析视图
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}
/**
* View接口
*/
public interface View {
/**
* 获取内容类型
*/
String getContentType();
/**
* 渲染视图
*/
void render(Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
/**
* 视图解析器链
*/
public void viewResolverChain() {
/*
* SpringMVC支持多个ViewResolver形成解析链:
*
* 1. BeanNameViewResolver (优先级最高)
* - 将视图名称作为Bean名称查找View Bean
*
* 2. ContentNegotiatingViewResolver
* - 根据请求的内容类型选择合适的视图
*
* 3. InternalResourceViewResolver
* - 解析JSP视图
*
* 4. FreeMarkerViewResolver
* - 解析FreeMarker模板
*
* 5. ThymeleafViewResolver
* - 解析Thymeleaf模板
*/
}
}
2. 常用视图解析器
2.1 InternalResourceViewResolver
<!-- JSP视图解析器配置 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="order" value="1"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
/**
* JSP视图解析器Java配置
*/
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
resolver.setOrder(1);
resolver.setContentType("text/html;charset=UTF-8");
// 设置暴露的属性
resolver.setExposeContextBeansAsAttributes(true);
resolver.setExposedContextBeanNames("userService", "roleService");
return resolver;
}
}
/**
* JSP视图使用示例
*/
@Controller
@RequestMapping("/jsp")
public class JspViewController {
@GetMapping("/user/list")
public String userList(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
model.addAttribute("title", "用户列表");
// 返回逻辑视图名,解析为:/WEB-INF/views/user/list.jsp
return "user/list";
}
@GetMapping("/user/detail/{id}")
public ModelAndView userDetail(@PathVariable Long id) {
User user = userService.findById(id);
ModelAndView mav = new ModelAndView("user/detail");
mav.addObject("user", user);
mav.addObject("title", "用户详情");
return mav;
}
}
2.2 BeanNameViewResolver
/**
* Bean名称视图解析器
*/
@Configuration
public class BeanNameViewConfig {
@Bean
public ViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(0); // 最高优先级
return resolver;
}
/**
* 自定义JSON视图
*/
@Bean("jsonView")
public View jsonView() {
return new AbstractView() {
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType("application/json;charset=UTF-8");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(model);
response.getWriter().write(json);
}
};
}
/**
* 自定义XML视图
*/
@Bean("xmlView")
public View xmlView() {
return new AbstractView() {
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType("application/xml;charset=UTF-8");
StringBuilder xml = new StringBuilder();
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
xml.append("<data>\n");
for (Map.Entry<String, Object> entry : model.entrySet()) {
xml.append(" <").append(entry.getKey()).append(">")
.append(entry.getValue())
.append("</").append(entry.getKey()).append(">\n");
}
xml.append("</data>");
response.getWriter().write(xml.toString());
}
};
}
}
/**
* 使用Bean名称视图
*/
@Controller
@RequestMapping("/api")
public class ApiViewController {
@GetMapping("/user/{id}")
public String getUserJson(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
// 返回Bean名称,使用jsonView Bean
return "jsonView";
}
@GetMapping("/user/{id}/xml")
public String getUserXml(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
// 返回Bean名称,使用xmlView Bean
return "xmlView";
}
}
2.3 ContentNegotiatingViewResolver
/**
* 内容协商视图解析器
*/
@Configuration
public class ContentNegotiationConfig {
@Bean
public ViewResolver contentNegotiatingViewResolver() {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setOrder(0);
// 设置内容协商管理器
resolver.setContentNegotiationManager(contentNegotiationManager());
// 设置默认视图
List<View> defaultViews = new ArrayList<>();
defaultViews.add(jsonView());
defaultViews.add(xmlView());
resolver.setDefaultViews(defaultViews);
return resolver;
}
@Bean
public ContentNegotiationManager contentNegotiationManager() {
ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
// 设置媒体类型映射
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
mediaTypes.put("html", MediaType.TEXT_HTML);
factory.setMediaTypes(mediaTypes);
// 设置协商策略
factory.setFavorPathExtension(true); // 支持路径扩展名
factory.setFavorParameter(true); // 支持请求参数
factory.setParameterName("format"); // 参数名称
factory.setIgnoreAcceptHeader(false); // 不忽略Accept头
factory.setDefaultContentType(MediaType.TEXT_HTML);
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public View jsonView() {
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setPrettyPrint(true);
view.setContentType("application/json;charset=UTF-8");
return view;
}
@Bean
public View xmlView() {
MappingJackson2XmlView view = new MappingJackson2XmlView();
view.setPrettyPrint(true);
view.setContentType("application/xml;charset=UTF-8");
return view;
}
}
/**
* 内容协商控制器
*/
@Controller
@RequestMapping("/content")
public class ContentNegotiationController {
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
// 根据请求的内容类型自动选择视图:
// /content/user/1.json -> JSON视图
// /content/user/1.xml -> XML视图
// /content/user/1.html -> HTML视图
// /content/user/1?format=json -> JSON视图
return "user/detail";
}
}
3. 模板引擎集成
3.1 Thymeleaf集成
<!-- Thymeleaf依赖 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>
/**
* Thymeleaf配置
*/
@Configuration
public class ThymeleafConfig {
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
// 添加方言
templateEngine.addDialect(new SpringSecurityDialect());
templateEngine.addDialect(new LayoutDialect());
return templateEngine;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(false); // 开发环境设为false
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setContentType("text/html;charset=UTF-8");
return viewResolver;
}
@Autowired
private ApplicationContext applicationContext;
}
/**
* Thymeleaf控制器
*/
@Controller
@RequestMapping("/thymeleaf")
public class ThymeleafController {
@GetMapping("/user/list")
public String userList(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
model.addAttribute("title", "用户列表");
model.addAttribute("currentTime", new Date());
return "user/list"; // 解析为:/WEB-INF/templates/user/list.html
}
@GetMapping("/user/form")
public String userForm(Model model) {
model.addAttribute("user", new User());
model.addAttribute("roles", roleService.findAll());
return "user/form";
}
}
<!-- Thymeleaf模板示例:/WEB-INF/templates/user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}">用户列表</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1 th:text="${title}">用户列表</h1>
<p>当前时间:<span th:text="${#dates.format(currentTime, 'yyyy-MM-dd HH:mm:ss')}"></span></p>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1</td>
<td th:text="${user.username}">admin</td>
<td th:text="${user.email}">admin@example.com</td>
<td>
<span th:if="${user.status == 1}" class="badge badge-success">正常</span>
<span th:if="${user.status == 0}" class="badge badge-danger">禁用</span>
</td>
<td>
<a th:href="@{/thymeleaf/user/detail/{id}(id=${user.id})}" class="btn btn-sm btn-info">查看</a>
<a th:href="@{/thymeleaf/user/edit/{id}(id=${user.id})}" class="btn btn-sm btn-warning">编辑</a>
<a th:href="@{/thymeleaf/user/delete/{id}(id=${user.id})}"
class="btn btn-sm btn-danger"
onclick="return confirm('确定要删除吗?')">删除</a>
</td>
</tr>
</tbody>
</table>
<div th:if="${#lists.isEmpty(users)}">
<p class="text-muted">暂无用户数据</p>
</div>
</div>
</body>
</html>
3.2 FreeMarker集成
<!-- FreeMarker依赖 -->
<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.21</version>
</dependency>
/**
* FreeMarker配置
*/
@Configuration
public class FreeMarkerConfig {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/templates/");
configurer.setDefaultEncoding("UTF-8");
// FreeMarker设置
Properties settings = new Properties();
settings.setProperty("template_update_delay", "0");
settings.setProperty("default_encoding", "UTF-8");
settings.setProperty("number_format", "0.##########");
settings.setProperty("datetime_format", "yyyy-MM-dd HH:mm:ss");
settings.setProperty("classic_compatible", "true");
settings.setProperty("template_exception_handler", "ignore");
configurer.setFreemarkerSettings(settings);
return configurer;
}
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(false);
resolver.setPrefix("");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setRequestContextAttribute("request");
resolver.setExposeRequestAttributes(true);
resolver.setExposeSessionAttributes(true);
resolver.setExposeSpringMacroHelpers(true);
resolver.setOrder(2);
return resolver;
}
}
/**
* FreeMarker控制器
*/
@Controller
@RequestMapping("/freemarker")
public class FreeMarkerController {
@GetMapping("/user/list")
public String userList(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
model.addAttribute("title", "用户列表");
return "user/list"; // 解析为:/WEB-INF/templates/user/list.ftl
}
}
<!-- FreeMarker模板示例:/WEB-INF/templates/user/list.ftl -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${title}</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>${title}</h1>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.email}</td>
<td>
<#if user.status == 1>
<span class="badge badge-success">正常</span>
<#else>
<span class="badge badge-danger">禁用</span>
</#if>
</td>
<td>${user.createTime?string("yyyy-MM-dd HH:mm:ss")}</td>
<td>
<a href="/freemarker/user/detail/${user.id}" class="btn btn-sm btn-info">查看</a>
<a href="/freemarker/user/edit/${user.id}" class="btn btn-sm btn-warning">编辑</a>
<a href="/freemarker/user/delete/${user.id}"
class="btn btn-sm btn-danger"
onclick="return confirm('确定要删除吗?')">删除</a>
</td>
</tr>
<#else>
<tr>
<td colspan="6" class="text-center text-muted">暂无用户数据</td>
</tr>
</#list>
</tbody>
</table>
</div>
</body>
</html>
3.3 Velocity集成(已废弃,仅作了解)
/**
* Velocity配置(Spring 5.x已不支持)
*/
@Configuration
public class VelocityConfig {
// 注意:Spring 5.x已移除对Velocity的支持
// 此配置仅适用于Spring 4.x及以下版本
@Bean
public VelocityConfigurer velocityConfigurer() {
VelocityConfigurer configurer = new VelocityConfigurer();
configurer.setResourceLoaderPath("/WEB-INF/templates/");
Properties velocityProperties = new Properties();
velocityProperties.setProperty("input.encoding", "UTF-8");
velocityProperties.setProperty("output.encoding", "UTF-8");
configurer.setVelocityProperties(velocityProperties);
return configurer;
}
@Bean
public VelocityViewResolver velocityViewResolver() {
VelocityViewResolver resolver = new VelocityViewResolver();
resolver.setSuffix(".vm");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setOrder(3);
return resolver;
}
}
4. 视图技术选择
4.1 技术对比
/**
* 视图技术对比分析
*/
public class ViewTechnologyComparison {
/**
* JSP技术特点
*/
public void jspFeatures() {
/*
* 优点:
* 1. Java EE标准,广泛支持
* 2. 与Java代码紧密集成
* 3. 丰富的标签库(JSTL)
* 4. IDE支持良好
*
* 缺点:
* 1. 编译慢,开发效率低
* 2. 不支持模板继承
* 3. 语法相对复杂
* 4. 不适合前后端分离
*
* 适用场景:
* - 传统的MVC架构
* - 需要与Java代码紧密集成
* - 团队熟悉JSP技术
*/
}
/**
* Thymeleaf技术特点
*/
public void thymeleafFeatures() {
/*
* 优点:
* 1. 自然模板,可直接在浏览器打开
* 2. 强大的表达式语言
* 3. 良好的HTML5支持
* 4. 支持模板继承和片段
* 5. 国际化支持良好
*
* 缺点:
* 1. 学习成本相对较高
* 2. 性能不如JSP
* 3. 调试相对困难
*
* 适用场景:
* - 现代Web应用
* - 需要良好的HTML5支持
* - 前后端协作开发
*/
}
/**
* FreeMarker技术特点
*/
public void freeMarkerFeatures() {
/*
* 优点:
* 1. 功能强大,语法简洁
* 2. 性能优秀
* 3. 支持宏定义和模板继承
* 4. 良好的错误提示
*
* 缺点:
* 1. 学习成本较高
* 2. 模板不是有效的HTML
* 3. IDE支持一般
*
* 适用场景:
* - 复杂的模板逻辑
* - 性能要求较高
* - 代码生成工具
*/
}
}
4.2 选择建议
/**
* 视图技术选择建议
*/
public class ViewTechnologySelection {
/**
* 基于项目特点选择
*/
public void selectionCriteria() {
/*
* 1. 新项目建议:
* - 优先选择Thymeleaf
* - 考虑前后端分离架构
*
* 2. 遗留项目:
* - 继续使用JSP
* - 逐步迁移到现代模板引擎
*
* 3. 性能敏感项目:
* - 考虑FreeMarker
* - 或者前后端分离+REST API
*
* 4. 团队技能:
* - 根据团队熟悉程度选择
* - 考虑学习成本和维护成本
*/
}
/**
* 混合使用策略
*/
public void hybridStrategy() {
/*
* 可以在同一个项目中使用多种视图技术:
*
* 1. 管理后台:使用Thymeleaf
* 2. API接口:返回JSON/XML
* 3. 报表页面:使用FreeMarker
* 4. 错误页面:使用JSP
*/
}
}
5. 视图层最佳实践
5.1 模板组织结构
/WEB-INF/templates/
├── layout/ # 布局模板
│ ├── base.html # 基础布局
│ ├── admin.html # 管理后台布局
│ └── mobile.html # 移动端布局
├── fragments/ # 模板片段
│ ├── header.html # 页头
│ ├── footer.html # 页脚
│ ├── sidebar.html # 侧边栏
│ └── pagination.html # 分页组件
├── user/ # 用户模块
│ ├── list.html
│ ├── detail.html
│ ├── form.html
│ └── profile.html
├── role/ # 角色模块
│ ├── list.html
│ ├── form.html
│ └── permissions.html
├── common/ # 通用页面
│ ├── login.html
│ ├── register.html
│ └── dashboard.html
└── error/ # 错误页面
├── 404.html
├── 500.html
└── error.html
5.2 模板继承和片段
<!-- 基础布局模板:layout/base.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${title}">默认标题</title>
<!-- 通用CSS -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/common.css">
<!-- 页面特定CSS -->
<th:block th:fragment="css"></th:block>
</head>
<body>
<!-- 页头 -->
<div th:replace="fragments/header :: header"></div>
<!-- 主要内容 -->
<main class="container-fluid">
<div th:replace="${content} :: content"></div>
</main>
<!-- 页脚 -->
<div th:replace="fragments/footer :: footer"></div>
<!-- 通用JS -->
<script src="/js/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<!-- 页面特定JS -->
<th:block th:fragment="scripts"></th:block>
</body>
</html>
<!-- 页头片段:fragments/header.html -->
<header th:fragment="header" class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">SSM Demo</a>
<div class="navbar-nav ml-auto">
<a class="nav-link" href="/user/list">用户管理</a>
<a class="nav-link" href="/role/list">角色管理</a>
<div class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown">
<span th:text="${session.currentUser?.username}">用户</span>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/user/profile">个人资料</a>
<a class="dropdown-item" href="/logout">退出登录</a>
</div>
</div>
</div>
</div>
</header>
<!-- 具体页面:user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:replace="layout/base :: layout(~{::content}, ~{::css}, ~{::scripts})">
<head>
<!-- 页面特定CSS -->
<th:block th:fragment="css">
<link rel="stylesheet" href="/css/user.css">
</th:block>
</head>
<body>
<!-- 页面内容 -->
<div th:fragment="content">
<div class="row">
<div class="col-12">
<h1>用户列表</h1>
<!-- 搜索表单 -->
<form class="mb-3" method="get">
<div class="row">
<div class="col-md-3">
<input type="text" name="username" class="form-control"
placeholder="用户名" th:value="${param.username}">
</div>
<div class="col-md-3">
<select name="status" class="form-control">
<option value="">全部状态</option>
<option value="1" th:selected="${param.status == '1'}">正常</option>
<option value="0" th:selected="${param.status == '0'}">禁用</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="/user/create" class="btn btn-success">添加用户</a>
</div>
</div>
</form>
<!-- 用户表格 -->
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.email}"></td>
<td>
<span th:if="${user.status == 1}" class="badge badge-success">正常</span>
<span th:if="${user.status == 0}" class="badge badge-danger">禁用</span>
</td>
<td th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm')}"></td>
<td>
<a th:href="@{/user/detail/{id}(id=${user.id})}" class="btn btn-sm btn-info">查看</a>
<a th:href="@{/user/edit/{id}(id=${user.id})}" class="btn btn-sm btn-warning">编辑</a>
<button type="button" class="btn btn-sm btn-danger"
th:onclick="'deleteUser(' + ${user.id} + ')'">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页组件 -->
<div th:replace="fragments/pagination :: pagination(${page})"></div>
</div>
</div>
</div>
<!-- 页面特定JS -->
<th:block th:fragment="scripts">
<script>
function deleteUser(userId) {
if (confirm('确定要删除这个用户吗?')) {
$.post('/user/delete/' + userId, function(result) {
if (result.success) {
location.reload();
} else {
alert('删除失败:' + result.message);
}
});
}
}
</script>
</th:block>
</body>
</html>
6. 小结
本文深入介绍了SpringMVC的视图解析和模板引擎:
- 视图解析原理:从逻辑视图名到具体视图的解析过程
- 视图解析器:各种解析器的配置和使用场景
- 模板引擎集成:Thymeleaf、FreeMarker等的集成配置
- 技术选择:不同视图技术的特点和选择建议
- 最佳实践:模板组织、继承和片段化设计
掌握视图解析的关键点:
- 理解视图解析的完整流程
- 选择合适的视图技术
- 合理组织模板结构
- 使用模板继承和片段
- 考虑性能和维护性
🔗 下一篇预告
下一篇文章将介绍SpringMVC拦截器与异常处理,学习如何实现横切关注点和统一异常处理。
相关文章: