DSL(Domain Specific Language)是針對某一領域,具有受限表達性的一種計算機程式設計語言,
常用于聚焦指定的領域或問題,這就要求 DSL 具備強大的表現力,同時在使用起來要簡單,由于其使用簡單的特性,DSL 通常不會像 Java,C++等語言將其應用于一般性的編程任務,
對于 Groovy 來說,一個偉大的 DSL 產物就是新一代構建工具——Gradle,接下來讓我們看下有哪些特性來支撐Groovy方便的撰寫DSL:
一、原理
1、閉包
官方定義是“Groovy中的閉包是一個開放,匿名的代碼塊,可以接受引數,回傳值并分配給變數”
簡而言之,他說一個匿名的代碼塊,可以接受引數,有回傳值,在DSL中,一個DSL腳本就是一個閉包,
比如:
//執行一句話
{ printf 'Hello World' }
//閉包有默認引數it,且不用申明
{ println it }
//閉包有默認引數it,申明了也無所謂
{ it -> println it }
// name是自定義的引數名
{ name -> println name }
//多個引數的閉包
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
每定義的閉包是一個Closure物件,我們可以把一個閉包賦值給一個變數,然后呼叫變數執行
//閉包賦值
def closure = {
printf("hello")
}
//呼叫
closure()
2、括號語法
當呼叫的方法需要引數時,Groovy 不要求使用括號,若有多個引數,那么引數之間依然使用逗號分隔;如果不需要引數,那么方法的呼叫必須顯示的使用括號,
def add(number) { 1 + number }
//DSL呼叫
def res = add 1
println res
也支持級聯呼叫方式,舉例來說,a b c d 實際上就等同于 a(b).c(d)
//定義
total = 0
def a(number) {
total += number
return this
}
def b(number) {
total *= number
return this
}
//dsl
a 2 b 3
println total
3、無參方法呼叫
我們結合 Groovy 中對屬性的訪問就是對 getXXX 的訪問,將無引數的方法名改成 getXXX 的形式,即可實作“呼叫無引數的方法不需要括號”的語法!比如:
def getTotal() { println "Total" }
//DSL呼叫
total
4、MOP
MOP:元物件協議,由 Groovy 語言中的一種協議,該協議的出現為元編程提供了優雅的解決方案,而 MOP 機制的核心就是 MetaClass,
有點類似于 Java 中的反射,但是在使用上卻比 Java 中的反射簡單的多,
常用的方法有:
- invokeMethod()
- setProperty()
- hasProperty()
- methodMissing()
以下是一個methodMissing的例子:
detailInfo = [:]
def methodMissing(String name, args) {
detailInfo[name] = args
}
def introduce(closure) {
closure.delegate = this
closure()
detailInfo.each {
key, value ->
println "My $key is $value"
}
}
introduce {
name "zx"
age 18
}
5、定義和腳本分離
@BaseScript 需要在注釋在自定義的腳本型別變數上,來指定當前腳本屬于哪個Delegate,從而執行相應的腳本命令,也使IDE有自動提示的功能:
腳本定義
abstract class DslDelegate extends Script {
def setName(String name){
println name
}
}
腳本:
import dsl.groovy.SetNameDelegate
import groovy.transform.BaseScript
@BaseScript DslDelegate _
setName("name")
6、閉包委托
使用以上介紹的方法,只能在腳本里執行單個命令,如果想在腳本里執行復雜的嵌套關系,比如Gradle中的dependencies,就需要@DelegatesTo支持了,@DelegatesTo執行了腳本里定義的閉包用那個類來決議,
上面提到一個DSL腳本就是一個閉包,這里的DelegatesTo其實定義的是閉包里面的二級閉包的格式,當然如果你樂意,可以無限嵌套定義,
//定義二級閉包格式
class Conf{
String name
int age
Conf name(String name) {
this.name = name
return this
}
Conf age(int age) {
this.age = age
return this
}
}
//定義一級閉包格式,即腳本的格式
String user(@DelegatesTo(Conf.class) Closure<Conf> closure) {
Conf conf = new Conf()
DefaultGroovyMethods.with(conf, closure)
println "my name is ${conf.name} my age is ${conf.age}"
}
//dsl腳本
user{
name "tom"
age 12
}
7、加載并執行腳本
腳本可以在IDE里直接執行,大多數情況下DSL腳本都是以文本的形式存在資料庫或配置中,這時候就需要先加載腳本再執行,加載腳本可以通過以下方式:
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setScriptBaseClass(DslDelegate.class.getName());
GroovyShell shell = new GroovyShell(GroovyScriptRunner.class.getClassLoader());
Script script = shell.parse(file);
給腳本傳引數,并得到回傳結果:
Binding binding = new Binding();
binding.setProperty("key", anyValue);
Object res = InvokerHelper.createScript(script.getClass(), binding).run()
二、總結
通過以上的原理,你應該能設計出自己的DSL了,通過DSL可以設計出非常簡潔的API給用戶,在執行的時候呼叫DSL內部的復雜功能,這些功能的背后邏輯隱藏在了自己撰寫的Delegate中,
為了加深理解,我寫了個開源專案,把上面知識點串起來,構建了一個較完整的DSL流程,如果還有什么不懂的地方,歡迎留言交流,
專案地址:https://github.com/sofn/dsl-groovy
三、參考
官方MOP:https://groovy-lang.org/metaprogramming.html
領域專屬語言:https://wiki.jikexueyuan.com/project/groovy-introduction/domain-specific-languages.html
實戰Groovy系列:https://wizardforcel.gitbooks.io/ibm-j-pg/content/index.html
本文作者:木小豐,美團Java高級工程師,關注架構、軟體工程、全堆疊等,不定期分享軟體研發程序中的實踐、思考,
公共號:Java研發
本文博客鏈接:https://lesofn.com/archives/shi-yong-groovy-gou-jian-dsl

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266212.html
標籤:Java
