Spring Boot 使用 Kotlin Script Template 模板引擎kts 开发web应用

简介: Spring Boot 使用 Kotlin Script Template 模板引擎kts 开发web应用在 Spring Framework 5.0 M4 中引入了一个专门的Kotlin支持。

Spring Boot 使用 Kotlin Script Template 模板引擎kts 开发web应用

在 Spring Framework 5.0 M4 中引入了一个专门的Kotlin支持。

Kotlin Script based templates

ScriptTemplateView

从4.3版本开始,Spring Framework提供了一个 org.springframework.web.servlet.view.script.ScriptTemplateView 使用支持 JSR-223 的脚本引擎来渲染模板。

...

/**
 * An {@link AbstractUrlBasedView} subclass designed to run any template library
 * based on a JSR-223 script engine.
 *
 * <p>If not set, each property is auto-detected by looking up a single
 * {@link ScriptTemplateConfig} bean in the web application context and using
 * it to obtain the configured properties.
 *
 * <p>Nashorn Javascript engine requires Java 8+, and may require setting the
 * {@code sharedEngine} property to {@code false} in order to run properly. See
 * {@link ScriptTemplateConfigurer#setSharedEngine(Boolean)} for more details.
 *
 * @author Sebastien Deleuze
 * @author Juergen Hoeller
 * @since 4.2
 * @see ScriptTemplateConfigurer
 * @see ScriptTemplateViewResolver
 */
public class ScriptTemplateView extends AbstractUrlBasedView {

    public static final String DEFAULT_CONTENT_TYPE = "text/html";

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:";


    private static final ThreadLocal<Map<Object, ScriptEngine>> enginesHolder =
            new NamedThreadLocal<>("ScriptTemplateView engines");


    private ScriptEngine engine;

    private String engineName;

    private Locale locale;

    private Boolean sharedEngine;

    private String[] scripts;

    private String renderObject;

    private String renderFunction;

    private Charset charset;

    private String[] resourceLoaderPaths;

    private ResourceLoader resourceLoader;

    private volatile ScriptEngineManager scriptEngineManager;

...

}

这样我们就可以使用 kotlinx.html DSL或简单的Kotlin multiline String插值,编写kts类型安全模板。例如:

import com.easy.kotlin.kotlin_script_template.*

"""
${include("header")}
<p>Locale:
<a href="/?locale=fr">FR</a> |
<a href="/?locale=en">EN</a> |
<a href="/?locale=zh">中文</a>

</p>
<h1>${i18n("title")}</h1>
${include("users", mapOf(Pair("users", users)))}
${include("footer")}
"""


新建一个SpringBoot + Kotlin 工程

首先,我们新建一个SpringBoot + Kotlin 工程,使用对应的版本分别是

  • kotlinVersion = '1.1.2'
  • springBootVersion = '2.0.0.BUILD-SNAPSHOT'

使用gradle构建,配置如下:

buildscript {
    ext {
        kotlinVersion = '1.1.2'
        springBootVersion = '2.0.0.BUILD-SNAPSHOT'
    }
    repositories {
        mavenCentral()
        maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" }
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'

jar {
    baseName = 'kotlin-script-templating'
    version = '0.0.1-SNAPSHOT'
}

repositories {
    mavenCentral()
    maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" }
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}


dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
    compile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
    compile("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}")
    compile("org.jetbrains.kotlin:kotlin-script-util:${kotlinVersion}") {
        exclude group: "com.jcabi", module: "jcabi-aether"
        exclude group: "org.apache.maven", module: "maven-core"
        exclude group: "org.sonatype.aether", module: "aether-api"
    }
    testCompile("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
}

工程目录结构

工程目录结构如下:

.
├── README.md
├── build
│   └── kotlin-build
│       └── caches
│           └── version.txt
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    ├── main
    │   ├── java
    │   ├── kotlin
    │   │   └── com
    │   │       └── easy
    │   │           └── kotlin
    │   │               └── kotlin_script_template
    │   │                   ├── Application.kt
    │   │                   ├── Helpers.kt
    │   │                   ├── User.kt
    │   │                   └── ViewController.kt
    │   └── resources
    │       ├── META-INF
    │       │   └── services
    │       │       └── javax.script.ScriptEngineFactory
    │       ├── application.properties
    │       ├── banner.txt
    │       ├── messages.properties
    │       ├── messages_en.properties
    │       ├── messages_fr.properties
    │       ├── messages_zh.properties
    │       ├── scripts
    │       │   └── render.kts
    │       └── templates
    │           ├── footer.kts
    │           ├── header.kts
    │           ├── index.kts
    │           ├── user.kts
    │           └── users.kts
    └── test
        ├── java
        ├── kotlin
        │   └── com
        │       └── easy
        │           └── kotlin
        │               └── kotlin_script_template
        │                   └── ApplicationTests.kt
        └── resources

26 directories, 25 files

配置脚本解析引擎的实现类

在META-INF/services/javax.script.ScriptEngineFactory文件里面加上其脚本解析引擎的实现类

org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory

其中,完成kts模板文件编译解析的类是KotlinJsr223JvmLocalScriptEngineFactory,这个类在kotlin-script-util包里。
因为多了这一层编译解析,感觉速度慢了点。

配置Script Template Engine :


@SpringBootApplication
class Application : WebMvcConfigurerAdapter() {


    /**
     * ScriptTemplateConfigurer
     * scripts/render.kts
     */
    @Bean
    fun kotlinScriptConfigurer(): ScriptTemplateConfigurer {
        val configurer = ScriptTemplateConfigurer()
        configurer.engineName = "kotlin"
        configurer.setScripts("scripts/render.kts")
        configurer.renderFunction = "render"
        configurer.isSharedEngine = false
        return configurer
    }

    @Bean
    fun kotlinScriptViewResolver(): ViewResolver {
        val viewResolver = ScriptTemplateViewResolver()
        viewResolver.setPrefix("templates/")
        viewResolver.setSuffix(".kts")
        return viewResolver
    }

    @Bean
    fun localeResolver() = SessionLocaleResolver().apply {
        setDefaultLocale(Locale.CHINESE)
    }

    @Bean
    fun localeChangeInterceptor() = LocaleChangeInterceptor()

    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(localeChangeInterceptor())
    }

}

其中,LocaleChangeInterceptor() 是Spring框架里面定义的类

org.springframework.web.servlet.i18n.LocaleChangeInterceptor。

public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {

    /**
     * Default name of the locale specification parameter: "locale".
     */
    public static final String DEFAULT_PARAM_NAME = "locale";


    protected final Log logger = LogFactory.getLog(getClass());

    private String paramName = DEFAULT_PARAM_NAME;

    private String[] httpMethods;

    private boolean ignoreInvalidLocale = false;

    private boolean languageTagCompliant = false;

...

从上面的源码,我们可以看出它的默认query参数是locale

渲染脚本render.kts

其中,render.kts代码如下:

import org.springframework.web.servlet.view.script.RenderingContext
import org.springframework.context.support.ResourceBundleMessageSource
import javax.script.*
import org.springframework.beans.factory.getBean

// TODO Use engine.eval(String, Bindings) when https://youtrack.jetbrains.com/issue/KT-15450 will be fixed
fun render(template: String, model: Map<String, Any>, renderingContext: RenderingContext): String {
    val engine = ScriptEngineManager().getEngineByName("kotlin")
    val bindings = SimpleBindings(model)
    var messageSource = renderingContext.applicationContext.getBean<ResourceBundleMessageSource>()
    bindings.put("i18n", { code: String -> messageSource.getMessage(code, null, renderingContext.locale) })
    bindings.put("include", { path: String -> renderingContext.templateLoader.apply("templates/$path.kts") })
    engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE)
    return engine.eval(template) as String
}

模板绑定 ScriptTemplateWithBindings

我们上面在bindings里面添加了

bindings.put("i18n", { code: String -> messageSource.getMessage(code, null, renderingContext.locale) })

在index.kts这样调用

import com.easy.kotlin.kotlin_script_template.*

"""
${include("header")}
<p>Locale:
<a href="/?locale=fr">FR</a> |
<a href="/?locale=en">EN</a> |
<a href="/?locale=zh">中文</a>

</p>
<h1>${i18n("title")}</h1>
${include("users", mapOf(Pair("users", users)))}
${include("footer")}
"""

这个i18n在ScriptTemplateWithBindings类里的扩展函数如下

package com.easy.kotlin.kotlin_script_template

import javax.script.ScriptContext
import javax.script.ScriptEngineManager
import javax.script.SimpleBindings
import kotlin.script.templates.standard.ScriptTemplateWithBindings

fun ScriptTemplateWithBindings.include(path: String, model: Map<String, Any>? = null) :String {
    val engine = ScriptEngineManager().getEngineByName("kotlin")
    var includeBindings = if (model != null) {
        val b = SimpleBindings(LinkedHashMap(model))
        b["include"] = bindings["include"]
        b["i18n"] = bindings["i18n"]
        b
    } else {
        val b = SimpleBindings(bindings)
        b.remove("kotlin.script.history")
        b
    }

    engine.setBindings(includeBindings, ScriptContext.ENGINE_SCOPE)
    val template = (bindings["include"] as (String) -> String).invoke(path)
    return engine.eval(template) as String
}

fun ScriptTemplateWithBindings.i18n(code: String) =
    (bindings["i18n"] as (String) -> String).invoke(code)

fun <T> Iterable<T>.joinToLine(function: (foo: T) -> String): String
  { return joinToString(separator = "\n") { foo -> function.invoke(foo) } }

var ScriptTemplateWithBindings.users: List<User>
  get() = bindings["users"] as List<User>
  set(value) { throw UnsupportedOperationException()}

var ScriptTemplateWithBindings.user: User
  get() = bindings["user"] as User
  set(value) { throw UnsupportedOperationException()}

var ScriptTemplateWithBindings.title: String
  get() = bindings["title"] as String
  set(value) { throw UnsupportedOperationException()}

写实体类Usr,Controller类,最后写Spring Boot main函数:

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}

运行测试

命令行运行

gradle bootRun

启动运行,访问:http://localhost:8080/

你将看到如下输出页面

螢幕快照 2017-06-05 00.13.24.png
螢幕快照 2017-06-05 00.14.04.png

小结

本章节工程源代码:

https://github.com/EasyKotlin/kotlin-script-templating

使用Kotlin编写Spring Boot应用程序越多,我们越觉得这两种技术有着共同的目标,让我们广大程序员可以使用

  • 富有表达性
  • 简短
  • 可读的代码

来更高效地编写应用程序,而Spring Framework 5 Kotlin支持将这些技术以更加自然,简单和强大的方式来展现给我们。

Kotlin可以用来编写 基于注解的Spring Boot应用程序 ,但作为一种新的 functional and reactive applications 也将是一种很好的尝试,期待未来Spring Framework 5.0 和 Kotlin 结合的开发实践。

参考资料

https://github.com/sdeleuze/kotlin-script-templating

相关文章
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
531 0
|
7月前
|
XML JSON Java
【SpringBoot(三)】从请求到响应再到视图解析与模板引擎,本文带你领悟SpringBoot请求接收全流程!
Springboot专栏第三章,从请求的接收到视图解析,再到thymeleaf模板引擎的使用! 本文带你领悟SpringBoot请求接收到渲染的使用全流程!
545 3
|
10月前
|
缓存 JSON 前端开发
第07课:Spring Boot集成Thymeleaf模板引擎
第07课:Spring Boot集成Thymeleaf模板引擎
872 0
第07课:Spring Boot集成Thymeleaf模板引擎
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
547 4
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 的使用
本文介绍了 Thymeleaf 在 Spring Boot 项目中的使用方法,包括访问静态页面、处理对象和 List 数据、常用标签操作等内容。通过示例代码展示了如何配置 404 和 500 错误页面,以及如何在模板中渲染对象属性和列表数据。同时总结了常用的 Thymeleaf 标签,如 `th:value`、`th:if`、`th:each` 等,并提供了官方文档链接以供进一步学习。
914 0
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 的使用
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
1046 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
缓存 Java 应用服务中间件
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
504 0
|
移动开发 前端开发 JavaScript
SpringBoot3 整合Thymeleaf 模板引擎
Thymeleaf 是一个基于 Java 的现代模板引擎,支持 HTML 原型,文件后缀为 .html,可直接在浏览器中查看静态效果。它与 Spring Boot 完美整合,默认配置即可使用,无需额外视图解析器设置。Thymeleaf 支持多种表达式(如变量、链接、国际化等)和 th 属性(如 th:text、th:if 等),适用于 Web 和非 Web 应用开发。通过 th:fragment、th:insert、th:replace 和 th:include 等属性,可以抽取和复用公共页面片段,并支持参数传递。
2044 12
|
XML Java 网络架构
使用 Spring Boot 公开 SOAP Web 服务端点:详细指南
使用 Spring Boot 公开 SOAP Web 服务端点:详细指南
1839 0
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
252 7