我为与Kotlin语言创建DSL提供了出色的支持,并为此投入了大量精力 。
此功能现在用于创建gradle构建文件 , 在Spring Webflux中定义路由, 使用kotlinx.html库创建html模板。
在这里,我将演示创建基于kotlin的DSL来表示Cloud Foundry应用清单内容 。
当以yaml文件表示时,清单示例如下所示:
applications:
- name: myapp
memory: 512M
instances: 1
path: target/someapp.jar
routes:
- somehost.com
- antother.com/path
envs:
ENV_NAME1: VALUE1
ENV_NAME2: VALUE2
这是我要针对的DSL类型:
cf {
name = "myapp"
memory = 512(M)
instances = 1
path = "target/someapp.jar"
routes {
+"somehost.com"
+"another.com/path"
}
envs {
env["ENV_NAME1"] = "VALUE1"
env["ENV_NAME2"] = "VALUE2"
}
}
获得基本结构
让我从一个看起来像这样的简单结构开始:
cf {
name = "myapp"
instances = 1
path = "target/someapp.jar"
}
并希望这种DSL映射到如下所示的结构:
data class CfManifest(
var name: String = "",
var instances: Int? = 0,
var path: String? = null
)
它将转换为带有Lambda表达式的Kotlin函数:
fun cf(init: CfManifest.() -> Unit) {
...
}
该参数如下所示:
() -> Unit
是相当不言自明的,是不带任何参数且不返回任何内容的lambda表达式。
我花了一段时间才想到的部分是这个经过修改的lambda表达式,称为带有接收器的Lambda表达式:
CfManifest.() -> Unit
按照我的理解,它有两件事:
1.它在包装函数的范围内定义了接收器类型的扩展函数-在我的情况下是 CfManifest
类别
2.现在,lambda表达式中的this
表示接收器函数。
鉴于此, cf
函数转换为:
fun cf(init: CfManifest.() -> Unit): CfManifest {
val manifest = CfManifest()
manifest.init()
return manifest
}
可以简单地表示为:
fun cf(init: CfManifest.() -> Unit) = CfManifest().apply(init)
所以现在我打电话给:
cf {
name = "myapp"
instances = 1
path = "target/someapp.jar"
}
转换为:
CFManifest().apply {
this.name = "myapp"
this.instances = 1
this.path = "target/someapp.jar"
}
更多DSL
扩展基本结构:
cf {
name = "myapp"
memory = 512(M)
instances = 1
path = "target/someapp.jar"
routes {
+"somehost.com"
+"another.com/path"
}
envs {
env["ENV_NAME1"] = "VALUE1"
env["ENV_NAME2"] = "VALUE2"
}
}
路由和env依次成为CfManifest
类上的方法,如下所示:
data class CfManifest(
var name: String = "",
var path: String? = null,
var memory: MEM? = null,
...
var routes: ROUTES? = null,
var envs: ENVS = ENVS()
) {
fun envs(block: ENVS.() -> Unit) {
this.envs = ENVS().apply(block)
}
...
fun routes(block: ROUTES.() -> Unit) {
this.routes = ROUTES().apply(block)
}
}
data class ENVS(
var env: MutableMap<String, String> = mutableMapOf()
)
data class ROUTES(
private val routes: MutableList<String> = mutableListOf()
) {
operator fun String.unaryPlus() {
routes.add(this)
}
}
查看routes
方法如何将接收器类型为ROUTES
的Lambda表达式接受,这使我可以定义如下表达式:
cf {
...
routes {
+"somehost.com"
+"another.com/path"
}
...
}
这里的另一个技巧是使用以下方法添加路线:
+"somehost.com"
使用Kotlin约定启用此功能,该约定将特定的方法名称转换为运算符 ,此处为unaryPlus方法。 对我来说很酷的事情是,该运算符仅在ROUTES实例的范围内可见!
利用Kotlin功能的DSL的另一个功能是指定内存的方式,它有两个部分-数字和修饰符2G,500M等。
通过DSL以稍微修改的方式将其指定为2(G)和500(M)。
它的实现方式是使用另一种Kotlin约定,如果一个类具有一个invoke
方法,则实例可以通过以下方式调用它:
class ClassWithInvoke() {
operator fun invoke(n: Int): String = "" + n
}
val c = ClassWithInvoke()
c(10)
因此,在CFManifest
类范围内将invoke
方法作为Int
的扩展功能实现,就可以实现这种DSL:
data class CfManifest(
var name: String = "",
...
) {
...
operator fun Int.invoke(m: MemModifier): MEM = MEM(this, m)
}
就我而言,这纯粹是实验,对于Kotlin和Kotlin DSL来说,我都是新手,因此很可能在此实现中有很多可以改进的地方,欢迎任何反馈和建议。 您可以使用此示例代码在我的github回购玩这里
翻译自: https://www.javacodegeeks.com/2017/07/cloud-foundry-application-manifest-using-kotlin-dsl.html