領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多Groovy 語言是建立領域特定語言 (DSL) 的優秀平臺。好的 DSL 可以使程式更加簡潔和富有表現力,同時提高程式設計師的生產力。然而,直到現在,這些 DSL 在編輯器中還沒有得到 Groovy-Eclipse 的直接支援。當大量使用 DSL 時,內容輔助、搜尋、懸停提示和導航等標準 IDE 功能就會失去價值。雖然現在已經可以編寫 Eclipse 外掛來擴充套件 Groovy-Eclipse,但這是一種重量級的方法,需要對 Eclipse API 有專門的瞭解。現在,Groovy-Eclipse 支援 DSL 描述符 (DSLDs),在 Groovy-Eclipse 中支援自定義 DSL 將變得顯著更容易。
3.m + 2.yd + 2.mi - 1.km
這是一個簡單且富有表現力的 DSL,但當你將它輸入到 Groovy-Eclipse 中的 Groovy 編輯器時(為簡潔起見,假設 $url
已在其他地方定義)
[caption id="attachment_8774" align="aligncenter" width="179"][/caption]
你會看到下劃線且沒有懸停提示,這意味著編輯器無法靜態解析 DSL 的表示式。使用 DSLD,可以*教會*編輯器這些自定義 DSL 背後的一些語義,併為懸停提示提供文件
[caption id="attachment_8775" align="aligncenter" width="683"][/caption]
要為距離 DSL 建立 DSL 描述符,只需在 Groovy 專案中新增一個檔案,其副檔名為 *.dsld*,內容如下
currentType( subType( Number ) ).accept {
property name:"m", type:"Distance",
doc: """A <code>meter</code> from <a href="$url">$url</a>"""
}
這個指令碼的意思是 *無論何時編輯器中當前評估的型別是 java.lang.Number
的子型別,就為其新增一個型別為 Distance
的 'm
' 屬性*。currentType(subType(Number))
部分稱為 *pointcut*(切入點),呼叫 property
的程式碼塊稱為 *contribution block*(貢獻塊)。稍後將詳細介紹這些概念。
上面的指令碼片段不是完整的 DSLD。它只添加了 'm
' 屬性。要完成實現,可以利用 Groovy 語法的全部能力
currentType( subType( Number ) ).accept {
[ m: "meter", yd: "yard", cm: "centimerter", mi: "mile", km: "kilometer"].each {
property name:it.key, type:"Distance",
doc: """A <code>${it.value}</code> from <a href="$url">$url</a>"""
}
}
這個簡單的例子表明,一個相對小的指令碼可以建立一些強大的 DSL 支援。
DSLD 增強了 Groovy-Eclipse 的型別推斷引擎,該引擎在編輯時在後臺執行。DSLD 由 IDE 評估,並根據需要由推斷引擎查詢。
一個 DSLD 指令碼包含一組切入點(pointcuts),每個切入點都與一個或多個貢獻塊(contribution blocks)相關聯。切入點大致描述了需要增強型別推斷的*位置*(即哪些上下文中的哪些型別),而貢獻塊描述了*如何*增強(即應該新增哪些屬性和方法)。
有許多可用的切入點,在DSLD 文件中有詳細描述和示例。隨著我們開始瞭解人們將如何建立指令碼以及他們需要哪種操作,可用切入點的集合可能會在 DSLD 的未來版本中擴充套件。
貢獻塊是 Groovy 程式碼塊,透過 accept
方法與切入點相關聯。在貢獻塊內部可以執行的兩個主要操作是 property
(我們之前已經介紹過)和 method
(向貢獻塊中正在分析的型別新增方法)。
*切入點(pointcut)*一詞借用了面向切面程式設計 (AOP)。實際上,DSLD 可以被視為一種 AOP 語言。DSLD 與像 AspectJ 這樣的典型 AOP 語言的主要區別在於,DSLD 操作的是正在編輯的程式的抽象語法樹,而像 AspectJ 這樣的語言操作的是已編譯程式的 Java 位元組碼。
在 Codehaus 的 wiki 上有完整的 DSLD 文件。在這裡,我將簡要介紹如何開始使用 DSLD。入門步驟如下
http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.6/
currentType(subType('groovy.lang.GroovyObject')).accept {
property name : 'newProp', type : String,
provider : 'Sample DSL',
doc : 'This is a sample. You should see this in content assist for all GroovyObjects:<pre>newProp</pre>'
}
在 DSLD 檔案內部,您應該會看到特定於 DSLD 的內容輔助和懸停提示(這來自於步驟 2 中新增的元 DSLD 指令碼)。它看起來像這樣:
this.newProp
您應該會看到 newProp
被正確高亮顯示,並且懸停時會顯示來自 DSLD 的文件,它看起來像這樣:您可以從 Groovy -> DSLD 首選項頁面檢視和管理工作空間中的所有 DSLD:
在這裡,您可以啟用/停用單個指令碼,以及選擇要編輯哪些指令碼。
重要提示:由於在實現 DSLD 時查詢和修復錯誤可能有些隱晦,強烈建議您執行以下操作
您的指令碼的編譯和執行時問題將在這兩個地方之一顯示。
對於一個更大的例子,我們來看一下 Grails 框架。Grails 約束 DSL 提供了一種宣告式的方式來驗證 Grails 領域類。它清晰簡潔,但如果沒有對該 DSL 的直接編輯支援,Grails 程式設計師將依賴外部文件,並且可能直到執行時才意識到語法錯誤。我們可以建立一個 DSLD 來解決這個問題
// only available in STS 2.7.0 and above
supportsVersion(grailsTooling:"2.7.0")
// a generic grails artifact is a class that is in a grails project, is not a script and is in one of the 'grails-app' folders
def grailsArtifact = { String folder ->
sourceFolderOfCurrentType("grails-app/" + folder) &
nature("com.springsource.sts.grails.core.nature") & (~isScript())
}
// define the various kinds of grails artifacts
def domainClass = grailsArtifact("domain")
// we only require domainClass, but we can also reference other kinds of artifacts here
def controllerClass = grailsArtifact("controllers")
def serviceClass = grailsArtifact("services")
def taglibClass = grailsArtifact("taglib")
// constraints
// The constraints DSL is only applicable inside of the static "constraints" field declaration
inClosure() & (domainClass & enclosingField(name("constraints") & isStatic()) &
(bind(props : properties()) & // 'bind' props to the collection of properties in the domain class
currentTypeIsEnclosingType())).accept {
provider = "Grails Constraints DSL" // this value will appear in content assist
// for each non-static property, there are numerous constraints "methods" that are available
// define them all here
for (prop in props) {
if (prop.isStatic()) {
continue
}
if (prop.type == ClassHelper.STRING_TYPE) {
method isStatic: true, name: prop.name, params: [blank:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [creditCard:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [email:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [url:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [matches:String], useNamedArgs:true
} else if (prop.type.name == Date.name) {
method isStatic: true, name: prop.name, params: [max:Date], useNamedArgs:true
method isStatic: true, name: prop.name, params: [min:Date], useNamedArgs:true
} else if (ClassHelper.isNumberType(prop.type)) {
method isStatic: true, name: prop.name, params: [max:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [min:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [scale:Number], useNamedArgs:true
} else if (prop.type.implementsInterface(ClassHelper.LIST_TYPE)) {
method isStatic: true, name: prop.name, params: [maxSize:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [minSize:Number], useNamedArgs:true
}
method isStatic: true, name: prop.name, params: [unique:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [size:Integer], useNamedArgs:true
method isStatic: true, name: prop.name, params: [notEqual:Object], useNamedArgs:true
method isStatic: true, name: prop.name, params: [nullable:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [range:Range], useNamedArgs:true
method isStatic: true, name: prop.name, params: [inList:List], useNamedArgs:true
}
}
如果您複製上面的 DSLD 指令碼並將其新增到 Grails 專案中的 DSLD 檔案中,STS 將學會識別約束語言。例如,在下面的簡單領域類中,您將在約束塊內部獲得如下內容輔助:
上面的指令碼可以調整以新增自定義文件。
儘管大多數 Groovy 和 Grails 使用者不實現自己的 DSL,但他們會消費 DSL(例如在 Grails、Gaelyk 中,透過構建器等)。因此,即使大多數 STS 使用者不會建立自己的 DSLD,他們也會從其他人建立的 DSLD 中受益。我們將與庫和 DSL 開發者密切合作,為 Groovy 生態系統的不同部分建立通用的 DSLD。
在 Groovy-Eclipse 的即將釋出的版本中,您可以看到對流行的基於 Groovy 的框架的支援將顯著增加。
DSLD 語言的核心實現現在已經可用,但我們將根據使用者需求和他們希望支援的 DSL 型別,對其進行調整。我們將實現更多的切入點,擴充套件文件,並努力在 Groovy-Eclipse 本身中附帶一些標準的 DSLD。
請嘗試使用此處或 wiki 上介紹的一些 DSLD,並透過此部落格文章、我們的問題跟蹤器或Groovy-Eclipse 郵件列表向我們提供反饋。