Skip to content

Spring MVC Script Views:让前端模板在服务端飞起来 🚀

概述

在现代 Web 开发中,我们经常面临一个问题:如何在服务端使用流行的前端模板引擎? 比如你想在 Spring Boot 应用中使用 React、Handlebars 或 Mustache 模板,但这些模板引擎原本是为浏览器环境设计的。

Spring MVC 的 Script Views 功能就是为了解决这个痛点而生的!它让你能够在服务端运行各种基于 JavaScript 的模板引擎,实现真正的同构渲染

TIP

同构渲染是指同一套模板代码既可以在服务端运行(用于 SEO 和首屏加载),也可以在客户端运行(用于交互和动态更新)。

核心原理:JSR-223 的魔法 ✨

Script Views 的核心原理基于 JSR-223 Java Scripting API。简单来说,它允许 Java 应用程序执行各种脚本语言的代码。

IMPORTANT

脚本引擎必须实现 ScriptEngineInvocable 接口才能与 Spring 集成。

支持的模板引擎生态系统

Spring MVC Script Views 支持多种模板引擎和脚本引擎的组合:

模板库脚本引擎适用场景
HandlebarsNashorn逻辑简单的模板,语法清晰
MustacheNashorn无逻辑模板,适合简单展示
ReactNashorn组件化开发,同构渲染
EJSNashorn类似 JSP 的语法,易于上手
ERBJRubyRuby 开发者的首选
String templatesJythonPython 风格的模板
Kotlin ScriptKotlin类型安全的模板开发

环境准备

在开始使用之前,你需要准备相应的依赖:

kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    // Mustache 模板通过 WebJars 引入
    implementation("org.webjars:mustache:4.2.0")
}
kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    // Kotlin 脚本支持
    implementation("org.jetbrains.kotlin:kotlin-script-util")
}

WARNING

Java 8+ 自带 Nashorn JavaScript 引擎,但在 Java 15+ 中已被移除。如果使用较新版本的 Java,需要单独添加 Nashorn 依赖。

实战案例:Mustache 模板集成

让我们通过一个完整的例子来看看如何在 Spring Boot 中集成 Mustache 模板。

1. 配置 ScriptTemplateConfigurer

kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate() 
    }

    @Bean
    fun scriptTemplateConfigurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"              // 指定脚本引擎
        setScripts("mustache.js")           // 加载模板库
        renderObject = "Mustache"           // 模板对象名称
        renderFunction = "render"           // 渲染函数名称
        // 设置模板文件位置(可选)
        resourceLoaderPath = "classpath:/templates/"
    }
}
xml
<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:script-template/>
</mvc:view-resolvers>

<mvc:script-template-configurer 
    engine-name="nashorn" 
    render-object="Mustache" 
    render-function="render">
    <mvc:script location="mustache.js"/>
</mvc:script-template-configurer>

2. 创建控制器

kotlin
@RestController
class ProductController {

    @GetMapping("/products/{id}")
    fun getProduct(@PathVariable id: Long, model: Model): String {
        // 模拟从数据库获取产品信息
        val product = Product(
            id = id,
            name = "Spring Boot 实战指南",
            price = 99.99,
            description = "深入学习 Spring Boot 框架"
        )
        
        // 添加数据到模型中
        model.addAttribute("product", product) 
        model.addAttribute("pageTitle", "产品详情")
        
        // 返回模板名称(不需要扩展名)
        return "product-detail"
    }
}

data class Product(
    val id: Long,
    val name: String,
    val price: Double,
    val description: String
)

3. 创建 Mustache 模板

templates/product-detail.mustache
html
<!DOCTYPE html>
<html>
<head>
    <title>{{pageTitle}}</title>
    <meta charset="UTF-8">
    <style>
        .product-card {
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 20px;
            margin: 20px;
            max-width: 500px;
        }
        .price {
            color: #e74c3c;
            font-size: 1.5em;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div class="product-card">
        <h1>{{product.name}}</h1>
        <p class="price">¥{{product.price}}</p>
        <p>{{product.description}}</p>
        <button onclick="addToCart({{product.id}})">
            加入购物车
        </button>
    </div>
    
    <script>
        function addToCart(productId) {
            alert('产品 ' + productId + ' 已加入购物车!');
        }
    </script>
</body>
</html>

高级用法:自定义渲染函数

对于一些复杂的模板引擎(如 Handlebars),你可能需要自定义渲染逻辑:

1. Handlebars 集成示例

kotlin
@Configuration
class HandlebarsConfig {

    @Bean
    fun scriptTemplateConfigurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        // 加载多个脚本文件
        setScripts("polyfill.js", "handlebars.js", "render.js") 
        renderFunction = "render"
        isSharedEngine = false  // 非线程安全引擎需要设置为 false
    }
}

2. 创建 polyfill.js

javascript
// polyfill.js - 为 Handlebars 提供浏览器环境模拟
var window = {};

3. 创建自定义渲染函数

javascript
// render.js - 自定义渲染逻辑
function render(template, model) {
    // 编译模板(生产环境应该缓存编译结果)
    var compiledTemplate = Handlebars.compile(template); 
    
    // 渲染模板
    return compiledTemplate(model);
}

CAUTION

当使用非线程安全的脚本引擎时,必须将 sharedEngine 设置为 false,这会为每个请求创建新的引擎实例,可能影响性能。

渲染函数参数详解

Spring 调用渲染函数时会传递三个参数:

javascript
function render(template, model, renderingContext) {
    // template: 模板内容字符串
    // model: 包含所有模型数据的 Map 对象
    // renderingContext: 渲染上下文,包含应用上下文、区域设置等信息
    
    console.log("模板内容:", template);
    console.log("模型数据:", JSON.stringify(model));
    console.log("应用上下文:", renderingContext.getApplicationContext());
    
    // 执行实际的模板渲染
    return Mustache.render(template, model);
}

性能优化建议

1. 模板缓存

kotlin
@Component
class TemplateCache {
    private val compiledTemplates = ConcurrentHashMap<String, Any>()
    
    fun getCompiledTemplate(templateName: String, compiler: (String) -> Any): Any {
        return compiledTemplates.computeIfAbsent(templateName, compiler) 
    }
}

2. 脚本引擎池化

kotlin
@Configuration
class ScriptEngineConfig {
    
    @Bean
    fun scriptTemplateConfigurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderFunction = "render"
        isSharedEngine = true  // 启用引擎共享以提高性能
    }
}

常见问题与解决方案

问题1:模板文件找不到

解决方案

确保模板文件放在正确的位置,默认是 src/main/resources/templates/

kotlin
@Bean
fun scriptTemplateConfigurer() = ScriptTemplateConfigurer().apply {
    // 明确指定模板文件路径
    resourceLoaderPath = "classpath:/templates/"
    // 其他配置...
}

问题2:脚本引擎初始化失败

常见原因

  • 缺少必要的依赖
  • 脚本文件路径错误
  • 脚本语法错误
kotlin
// 添加错误处理
@Bean
fun scriptTemplateConfigurer(): ScriptTemplateConfigurer {
    return try {
        ScriptTemplateConfigurer().apply {
            engineName = "nashorn"
            setScripts("mustache.js")
            renderFunction = "render"
        }
    } catch (e: Exception) {
        logger.error("脚本模板配置失败", e) 
        throw e
    }
}

总结

Spring MVC Script Views 为我们提供了一个强大的工具来实现服务端模板渲染,特别适合以下场景:

SEO 友好:服务端渲染确保搜索引擎能够正确索引内容
同构应用:同一套模板代码在服务端和客户端都能运行
性能优化:首屏渲染更快,用户体验更好
技术栈统一:前端开发者可以直接参与服务端模板开发

NOTE

虽然 Script Views 功能强大,但在选择时也要考虑性能影响。对于简单的服务端渲染需求,传统的 Thymeleaf 或 JSP 可能是更好的选择。

通过合理使用 Spring MVC Script Views,你可以构建出既现代又高效的 Web 应用程序! 🎉