Skip to content

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 的核心优势

  1. 类型安全:编译时检查,减少运行时错误
  2. DSL 语法:更接近程序员的思维方式
  3. 强大的表达能力:充分利用 Groovy 语言特性
  4. 更好的 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 MarkupThymeleafJSP
类型安全✅ 编译时检查❌ 运行时检查❌ 运行时检查
语法简洁性✅ DSL 语法⚠️ 属性语法❌ 标签混合
IDE 支持✅ 优秀✅ 良好⚠️ 一般
学习曲线⚠️ 需要 Groovy 基础✅ 容易上手✅ 容易上手
性能✅ 编译后高效✅ 良好✅ 良好

总结 🎯

Groovy Markup Template Engine 为 Spring MVC 提供了一种现代化、类型安全的模板解决方案。它特别适合:

  • API 优先的应用:需要生成 XML 或结构化数据
  • 类型安全要求高的项目:编译时错误检查
  • 复杂逻辑的模板:充分利用 Groovy 语言特性
  • 组件化开发:模板复用和组件化

IMPORTANT

虽然 Groovy Markup 功能强大,但需要团队具备一定的 Groovy 语言基础。在选择模板引擎时,应该综合考虑项目需求、团队技能和维护成本。

通过合理使用 Groovy Markup,你可以构建出既安全又高效的 Web 应用视图层,享受类型安全和强大表达能力带来的开发体验提升! 🚀