Appearance
Spring MVC Groovy Markup 模板引擎深度解析 🎨
什么是 Groovy Markup?
Groovy Markup Template Engine 是一个基于 Groovy 语言的模板引擎,专门用于生成类似 XML 的标记语言(XML、XHTML、HTML5 等)。虽然它主要针对标记语言,但实际上可以生成任何基于文本的内容。
NOTE
Groovy Markup Template Engine 需要 Groovy 2.3.1+ 版本支持。
为什么需要 Groovy Markup?🤔
在传统的 Web 开发中,我们经常面临以下痛点:
传统模板引擎的局限性
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>${title}</title>
</head>
<body>
<% if (user != null) { %>
<p>Hello, ${user.name}!</p>
<% } else { %>
<p>Please login</p>
<% } %>
</body>
</html>
groovy
yieldUnescaped '<!DOCTYPE html>'
html(lang:'zh-CN') {
head {
title(pageTitle)
}
body {
if (user) {
p("Hello, ${user.name}!")
} else {
p('Please login')
}
}
}
Groovy Markup 的核心优势
- 类型安全:编译时检查,减少运行时错误
- DSL 语法:更接近程序员的思维方式
- 强大的表达能力:充分利用 Groovy 语言特性
- 更好的 IDE 支持:语法高亮、自动补全等
Spring MVC 中的配置 ⚙️
基础配置
kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.groovy()
}
// 配置 Groovy Markup 模板引擎
@Bean
fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply {
resourceLoaderPath = "/WEB-INF/templates/"
cacheTemplates = true // 生产环境建议开启缓存
}
}
java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.groovy();
}
@Bean
public GroovyMarkupConfigurer groovyMarkupConfigurer() {
GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
configurer.setResourceLoaderPath("/WEB-INF/templates/");
configurer.setCacheTemplates(true); // 生产环境建议开启缓存
return configurer;
}
}
高级配置选项
kotlin
@Bean
fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply {
resourceLoaderPath = "/WEB-INF/templates/"
cacheTemplates = true
// 设置模板文件扩展名
suffix = ".tpl"
// 配置自动转义(防止XSS攻击)
autoEscape = true
// 设置字符编码
encoding = "UTF-8"
// 配置基础模板类
baseTemplateClass = "com.example.BaseTemplate"
}
TIP
在生产环境中,建议开启 cacheTemplates = true
以提高性能。开发环境可以设置为 false
以便实时看到模板变更。
实战示例:构建一个用户管理页面 💻
Controller 层
kotlin
@Controller
@RequestMapping("/users")
class UserController {
@GetMapping
fun listUsers(model: Model): String {
val users = listOf(
User(1, "张三", "[email protected]", true),
User(2, "李四", "[email protected]", false),
User(3, "王五", "[email protected]", true)
)
model.addAttribute("users", users)
model.addAttribute("pageTitle", "用户管理")
return "users/list" // 对应 /WEB-INF/templates/users/list.tpl
}
@GetMapping("/{id}")
fun userDetail(@PathVariable id: Long, model: Model): String {
val user = User(id, "用户$id", "user$id@example.com", true)
model.addAttribute("user", user)
return "users/detail"
}
}
data class User(
val id: Long,
val name: String,
val email: String,
val active: Boolean
)
模板文件:用户列表页面
点击查看完整的用户列表模板 (/WEB-INF/templates/users/list.tpl)
groovy
yieldUnescaped '<!DOCTYPE html>'
html(lang:'zh-CN') {
head {
meta(charset:'UTF-8')
meta(name:'viewport', content:'width=device-width, initial-scale=1.0')
title(pageTitle ?: '用户管理')
// 引入 Bootstrap CSS
link(rel:'stylesheet',
href:'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css')
}
body {
div(class:'container mt-4') {
// 页面标题
div(class:'row mb-4') {
div(class:'col') {
h1(class:'display-4') {
yield '👥 '
yield pageTitle
}
}
}
// 用户统计信息
div(class:'row mb-4') {
div(class:'col-md-4') {
div(class:'card text-center') {
div(class:'card-body') {
h5(class:'card-title', '总用户数')
p(class:'card-text display-6', users.size())
}
}
}
div(class:'col-md-4') {
div(class:'card text-center') {
div(class:'card-body') {
h5(class:'card-title', '活跃用户')
p(class:'card-text display-6 text-success',
users.count { it.active })
}
}
}
div(class:'col-md-4') {
div(class:'card text-center') {
div(class:'card-body') {
h5(class:'card-title', '非活跃用户')
p(class:'card-text display-6 text-warning',
users.count { !it.active })
}
}
}
}
// 用户表格
div(class:'row') {
div(class:'col') {
if (users.isEmpty()) {
div(class:'alert alert-info text-center') {
yield '📭 暂无用户数据'
}
} else {
table(class:'table table-striped table-hover') {
thead(class:'table-dark') {
tr {
th('ID')
th('姓名')
th('邮箱')
th('状态')
th('操作')
}
}
tbody {
users.each { user ->
tr {
td(user.id)
td {
strong(user.name)
}
td {
a(href:"mailto:${user.email}", user.email)
}
td {
if (user.active) {
span(class:'badge bg-success', '✅ 活跃')
} else {
span(class:'badge bg-secondary', '⏸️ 非活跃')
}
}
td {
a(class:'btn btn-sm btn-outline-primary me-2',
href:"/users/${user.id}", '查看详情')
button(class:'btn btn-sm btn-outline-warning',
type:'button', '编辑')
}
}
}
}
}
}
}
}
}
// 引入 Bootstrap JS
script(src:'https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js')
}
}
模板文件:用户详情页面
groovy
yieldUnescaped '<!DOCTYPE html>'
html(lang:'zh-CN') {
head {
meta(charset:'UTF-8')
title("用户详情 - ${user.name}")
link(rel:'stylesheet',
href:'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css')
}
body {
div(class:'container mt-4') {
// 面包屑导航
nav(ariaLabel:'breadcrumb') {
ol(class:'breadcrumb') {
li(class:'breadcrumb-item') {
a(href:'/users', '用户管理')
}
li(class:'breadcrumb-item active', ariaCurrentPage:'page', user.name)
}
}
// 用户信息卡片
div(class:'row justify-content-center') {
div(class:'col-md-8') {
div(class:'card') {
div(class:'card-header bg-primary text-white') {
h4(class:'mb-0') {
yield '👤 用户详情'
}
}
div(class:'card-body') {
dl(class:'row') {
dt(class:'col-sm-3', 'ID:')
dd(class:'col-sm-9', user.id)
dt(class:'col-sm-3', '姓名:')
dd(class:'col-sm-9') {
strong(user.name)
}
dt(class:'col-sm-3', '邮箱:')
dd(class:'col-sm-9') {
a(href:"mailto:${user.email}", user.email)
}
dt(class:'col-sm-3', '状态:')
dd(class:'col-sm-9') {
if (user.active) {
span(class:'badge bg-success fs-6', '✅ 活跃用户')
} else {
span(class:'badge bg-secondary fs-6', '⏸️ 非活跃用户')
}
}
}
}
div(class:'card-footer') {
a(class:'btn btn-primary me-2', href:'/users', '返回列表')
button(class:'btn btn-warning', type:'button', '编辑用户')
}
}
}
}
}
}
}
Groovy Markup 的核心特性 🚀
1. 强大的条件渲染
groovy
// 条件渲染示例
div(class:'user-status') {
if (user.active) {
span(class:'badge success', '在线')
} else if (user.lastLoginDays < 7) {
span(class:'badge warning', '最近活跃')
} else {
span(class:'badge danger', '长期未登录')
}
}
2. 循环和集合处理
groovy
// 处理集合数据
ul(class:'user-list') {
users.each { user ->
li(class:'user-item') {
yield "${user.name} (${user.email})"
}
}
}
// 带索引的循环
users.eachWithIndex { user, index ->
div(class:"user-row ${index % 2 == 0 ? 'even' : 'odd'}") {
yield "第${index + 1}位用户:${user.name}"
}
}
3. 模板复用和组件化
groovy
// 定义可复用的组件
def renderUserCard = { user ->
div(class:'card user-card') {
div(class:'card-body') {
h5(class:'card-title', user.name)
p(class:'card-text', user.email)
if (user.active) {
span(class:'badge bg-success', '活跃')
}
}
}
}
// 使用组件
div(class:'user-grid') {
users.each { user ->
renderUserCard(user)
}
}
最佳实践与注意事项 ⚠️
1. 安全性考虑
groovy
// ❌ 错误:直接输出用户输入(XSS 风险)
p(userInput)
// ✅ 正确:使用 yield 进行转义
p {
yield userInput
}
// ✅ 如果确实需要输出 HTML,使用 yieldUnescaped
div {
yieldUnescaped trustedHtmlContent
}
WARNING
永远不要直接输出未经验证的用户输入,这可能导致 XSS 攻击。
2. 性能优化
kotlin
@Bean
fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply {
resourceLoaderPath = "/WEB-INF/templates/"
cacheTemplates = true // [!code highlight] // 生产环境必须开启
// 预编译模板以提高性能
autoIndent = false // [!code highlight] // 减少输出大小
autoNewLine = false // [!code highlight] // 减少输出大小
}
3. 调试技巧
groovy
// 调试模式下输出变量信息
if (System.getProperty('debug') == 'true') {
comment("Debug: users.size() = ${users.size()}")
comment("Debug: pageTitle = ${pageTitle}")
}
与其他模板引擎的对比 📊
特性 | Groovy Markup | Thymeleaf | JSP |
---|---|---|---|
类型安全 | ✅ 编译时检查 | ❌ 运行时检查 | ❌ 运行时检查 |
语法简洁性 | ✅ DSL 语法 | ⚠️ 属性语法 | ❌ 标签混合 |
IDE 支持 | ✅ 优秀 | ✅ 良好 | ⚠️ 一般 |
学习曲线 | ⚠️ 需要 Groovy 基础 | ✅ 容易上手 | ✅ 容易上手 |
性能 | ✅ 编译后高效 | ✅ 良好 | ✅ 良好 |
总结 🎯
Groovy Markup Template Engine 为 Spring MVC 提供了一种现代化、类型安全的模板解决方案。它特别适合:
- API 优先的应用:需要生成 XML 或结构化数据
- 类型安全要求高的项目:编译时错误检查
- 复杂逻辑的模板:充分利用 Groovy 语言特性
- 组件化开发:模板复用和组件化
IMPORTANT
虽然 Groovy Markup 功能强大,但需要团队具备一定的 Groovy 语言基础。在选择模板引擎时,应该综合考虑项目需求、团队技能和维护成本。
通过合理使用 Groovy Markup,你可以构建出既安全又高效的 Web 应用视图层,享受类型安全和强大表达能力带来的开发体验提升! 🚀