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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 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

相关文章
|
6天前
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
32 0
|
6天前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 的使用
本文介绍了 Thymeleaf 在 Spring Boot 项目中的使用方法,包括访问静态页面、处理对象和 List 数据、常用标签操作等内容。通过示例代码展示了如何配置 404 和 500 错误页面,以及如何在模板中渲染对象属性和列表数据。同时总结了常用的 Thymeleaf 标签,如 `th:value`、`th:if`、`th:each` 等,并提供了官方文档链接以供进一步学习。
48 0
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 的使用
|
5月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
95 4
|
6天前
|
缓存 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`。这可避免因缓存导致页面未及时刷新的问题。
26 0
|
3月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
126 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
4月前
|
存储 Kotlin
正则表达式在Kotlin中的应用:提取图片链接
正则表达式在Kotlin中的应用:提取图片链接
|
4月前
|
XML Java 网络架构
使用 Spring Boot 公开 SOAP Web 服务端点:详细指南
使用 Spring Boot 公开 SOAP Web 服务端点:详细指南
319 0
|
5月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
106 4
|
6月前
|
监控 安全 Java
Kotlin 在公司上网监控中的安卓开发应用
在数字化办公环境中,公司对员工上网行为的监控日益重要。Kotlin 作为一种基于 JVM 的编程语言,具备简洁、安全、高效的特性,已成为安卓开发的首选语言之一。通过网络请求拦截,Kotlin 可实现网址监控、访问时间记录等功能,满足公司上网监控需求。其简洁性有助于快速构建强大的监控应用,并便于后续维护与扩展。因此,Kotlin 在安卓上网监控应用开发中展现出广阔前景。
50 1
|
5月前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
331 0

热门文章

最新文章