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

相关文章
|
7月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
576 4
|
11月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
11月前
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
7月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
7月前
|
XML JSON Java
【SpringBoot(三)】从请求到响应再到视图解析与模板引擎,本文带你领悟SpringBoot请求接收全流程!
Springboot专栏第三章,从请求的接收到视图解析,再到thymeleaf模板引擎的使用! 本文带你领悟SpringBoot请求接收到渲染的使用全流程!
530 3
|
8月前
|
存储 安全 Java
如何在 Spring Web 应用程序中使用 @SessionScope 和 @RequestScope
Spring框架中的`@SessionScope`和`@RequestScope`注解用于管理Web应用中的状态。`@SessionScope`绑定HTTP会话生命周期,适用于用户特定数据,如购物车;`@RequestScope`限定于单个请求,适合无状态、线程安全的操作,如日志记录。合理选择作用域能提升应用性能与可维护性。
331 1
|
10月前
|
缓存 JSON 前端开发
第07课:Spring Boot集成Thymeleaf模板引擎
第07课:Spring Boot集成Thymeleaf模板引擎
846 0
第07课:Spring Boot集成Thymeleaf模板引擎
|
9月前
|
存储 NoSQL Java
探索Spring Boot的函数式Web应用开发
通过这种方式,开发者能以声明式和函数式的编程习惯,构建高效、易测试、并发友好的Web应用,同时也能以较小的学习曲线迅速上手,因为这些概念与Spring Framework其他部分保持一致性。在设计和编码过程中,保持代码的简洁性和高内聚性,有助于维持项目的可管理性,也便于其他开发者阅读和理解。
244 0
|
10月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
695 0
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
2193 0

热门文章

最新文章